### Eclipse Workspace Patch 1.0 #P org.eclipse.jdt.core Index: compiler/org/eclipse/jdt/core/compiler/IProblem.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/core/compiler/IProblem.java,v retrieving revision 1.225.2.23 diff -u -r1.225.2.23 IProblem.java --- compiler/org/eclipse/jdt/core/compiler/IProblem.java 6 Jul 2011 18:46:34 -0000 1.225.2.23 +++ compiler/org/eclipse/jdt/core/compiler/IProblem.java 11 Jul 2011 17:34:40 -0000 @@ -1394,6 +1394,17 @@ int UnhandledExceptionOnAutoClose = TypeRelated + 882; /** @since 3.7 */ int DiamondNotBelow17 = TypeRelated + 883; + /** @since 3.7 */ + int PotentiallyUnclosedCloseable = Internal + 884; + /** @since 3.7 */ + int PotentiallyUnclosedCloseableAtExit = Internal + 885; + /** @since 3.7 */ + int UnclosedCloseable = Internal + 886; + /** @since 3.7 */ + int UnclosedCloseableAtExit = Internal + 887; + /** @since 3.7 */ + int ExplicitlyClosedAutoCloseable = Internal + 888; + /** * External problems -- These are problems defined by other plugins */ Index: compiler/org/eclipse/jdt/internal/compiler/ast/AllocationExpression.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/AllocationExpression.java,v retrieving revision 1.84.2.12 diff -u -r1.84.2.12 AllocationExpression.java --- compiler/org/eclipse/jdt/internal/compiler/ast/AllocationExpression.java 7 Jul 2011 17:47:46 -0000 1.84.2.12 +++ compiler/org/eclipse/jdt/internal/compiler/ast/AllocationExpression.java 11 Jul 2011 17:34:40 -0000 @@ -43,6 +43,13 @@ // process arguments if (this.arguments != null) { for (int i = 0, count = this.arguments.length; i < count; i++) { + FakedTrackingVariable trackVar = FakedTrackingVariable.getCloseTrackingVariable(this.arguments[i]); + if (trackVar != null) { + // insert info that the tracked resource *may* be closed (by the target method, i.e.) + FlowInfo infoResourceIsClosed = flowInfo.copy(); + infoResourceIsClosed.markAsDefinitelyNonNull(trackVar.binding); + flowInfo = FlowInfo.conditional(flowInfo, infoResourceIsClosed); + } flowInfo = this.arguments[i] .analyseCode(currentScope, flowContext, flowInfo) Index: compiler/org/eclipse/jdt/internal/compiler/ast/Assignment.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Assignment.java,v retrieving revision 1.94.2.3 diff -u -r1.94.2.3 Assignment.java --- compiler/org/eclipse/jdt/internal/compiler/ast/Assignment.java 16 Mar 2011 10:04:29 -0000 1.94.2.3 +++ compiler/org/eclipse/jdt/internal/compiler/ast/Assignment.java 11 Jul 2011 17:34:40 -0000 @@ -51,6 +51,33 @@ flowInfo = ((Reference) this.lhs) .analyseAssignment(currentScope, flowContext, flowInfo, this, false) .unconditionalInits(); + if (local != null) { + LocalVariableBinding trackerBinding = null; + if (local.closeTracker != null) { + // Assigning to a variable already holding an AutoCloseable, has it been closed before? + trackerBinding = local.closeTracker.binding; + if (!flowInfo.isDefinitelyNull(local)) { // only if previous value may be non-null + if (flowInfo.isDefinitelyNull(trackerBinding)) + currentScope.problemReporter().unclosedCloseable(local.closeTracker, this); + else if (flowInfo.isPotentiallyNull(trackerBinding)) + currentScope.problemReporter().potentiallyUnclosedCloseable(local.closeTracker, this); + } + } + if (FakedTrackingVariable.isAutoCloseable(this.expression.resolvedType)) { + if (local.closeTracker != null && local.closeTracker.isInsideTryWithResources) { + // re-assigning resource of try-with-resources is a different error + } else { + // new value is AutoCloseable, start tracking, possibly re-using existing tracker var: + if (trackerBinding == null) { + local.closeTracker = new FakedTrackingVariable(local, this); + trackerBinding = local.closeTracker.binding; + } + flowInfo.markAsDefinitelyNull(trackerBinding); +// if (flowContext.initsOnFinally != null) +// flowContext.initsOnFinally.markAsDefinitelyNonNull(trackerBinding); + } + } + } int nullStatus = this.expression.nullStatus(flowInfo); if (local != null && (local.type.tagBits & TagBits.IsBaseType) == 0) { if (nullStatus == FlowInfo.NULL) { Index: compiler/org/eclipse/jdt/internal/compiler/ast/Block.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/Block.java,v retrieving revision 1.43 diff -u -r1.43 Block.java --- compiler/org/eclipse/jdt/internal/compiler/ast/Block.java 14 Oct 2009 18:08:37 -0000 1.43 +++ compiler/org/eclipse/jdt/internal/compiler/ast/Block.java 11 Jul 2011 17:34:40 -0000 @@ -36,6 +36,8 @@ flowInfo = stat.analyseCode(this.scope, flowContext, flowInfo); } } + if (this.explicitDeclarations > 0) // if block has its own scope analyze tracking vars now: + this.scope.checkUnclosedCloseables(flowInfo, flowContext, null); return flowInfo; } /** Index: compiler/org/eclipse/jdt/internal/compiler/ast/FakedTrackingVariable.java =================================================================== RCS file: compiler/org/eclipse/jdt/internal/compiler/ast/FakedTrackingVariable.java diff -N compiler/org/eclipse/jdt/internal/compiler/ast/FakedTrackingVariable.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ compiler/org/eclipse/jdt/internal/compiler/ast/FakedTrackingVariable.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,103 @@ +/******************************************************************************* + * Copyright (c) 2011 GK Software AG 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: + * GK Software AG - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.internal.compiler.ast; + +import org.eclipse.jdt.internal.compiler.codegen.CodeStream; +import org.eclipse.jdt.internal.compiler.impl.Constant; +import org.eclipse.jdt.internal.compiler.lookup.BlockScope; +import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding; +import org.eclipse.jdt.internal.compiler.lookup.MethodScope; +import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; +import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; +import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; +import org.eclipse.jdt.internal.compiler.lookup.TypeIds; + +/** + * A faked local variable declaration used for keeping track of data flows of a + * special variable. Certain events will be recorded by changing the null info + * for this variable. + * + * @author Stephan Herrmann + */ +public class FakedTrackingVariable extends LocalDeclaration { + + /** If resource is already managed by try-with-resources don't complain. */ + public boolean isInsideTryWithResources; + + /** + * If close() is invoked from a nested method (inside a local type) + * report remaining problems only as potential. + */ + public boolean closedInNestedMethod; + + /** If a problem has already been reported don't complain again. */ + public boolean hasReportedProblem; + + MethodScope methodScope; // designates the method declaring this variable + + public LocalVariableBinding originalBinding; // the real local being tracked + + public FakedTrackingVariable(LocalVariableBinding original, Statement location) { + 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; + resolve(original.declaringScope); + } + + public void generateCode(BlockScope currentScope, CodeStream codeStream) + { /* NOP - this variable is completely dummy, ie. for analysis only. */ } + + public void resolve (BlockScope scope) { + // only need the binding, which is used as reference in FlowInfo methods. + this.binding = new LocalVariableBinding( + this.name, + scope.getJavaLangObject(), // dummy, just needs to be a reference type + 0, + false); + this.binding.setConstant(Constant.NotAConstant); + this.binding.useFlag = LocalVariableBinding.USED; + // use a free slot without assigning it: + this.binding.id = scope.registerTrackingVariable(this); + } + + /** + * If expression resolves to a local variable binding of type AutoCloseable, + * answer the variable that tracks closing of that local, creating it if needed. + * @param expression + * @return a new {@link FakedTrackingVariable} or null. + */ + public static FakedTrackingVariable getCloseTrackingVariable(Expression expression) { + 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 (local.isParameter() || !isAutoCloseable(expression.resolvedType)) + return null; + // tracking var doesn't yet exist. This happens in finally block + // which is analyzed before the corresponding try block + Statement location = local.declaration; + return local.closeTracker = new FakedTrackingVariable(local, location); + } + } + return null; + } + + /** Answer wither the given type binding is a subtype of java.lang.AutoCloseable. */ + public static boolean isAutoCloseable(TypeBinding typeBinding) { + return typeBinding instanceof ReferenceBinding + && ((ReferenceBinding)typeBinding).hasTypeBit(TypeIds.BitAutoCloseable); + } +} Index: compiler/org/eclipse/jdt/internal/compiler/ast/LocalDeclaration.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/LocalDeclaration.java,v retrieving revision 1.77.2.3 diff -u -r1.77.2.3 LocalDeclaration.java --- compiler/org/eclipse/jdt/internal/compiler/ast/LocalDeclaration.java 8 Mar 2011 17:20:09 -0000 1.77.2.3 +++ compiler/org/eclipse/jdt/internal/compiler/ast/LocalDeclaration.java 11 Jul 2011 17:34:41 -0000 @@ -70,12 +70,17 @@ } if ((this.initialization.implicitConversion & TypeIds.UNBOXING) != 0) { this.initialization.checkNPE(currentScope, flowContext, flowInfo); - } - + } flowInfo = this.initialization .analyseCode(currentScope, flowContext, flowInfo) .unconditionalInits(); + if (FakedTrackingVariable.isAutoCloseable(this.initialization.resolvedType)) { + this.binding.closeTracker = new FakedTrackingVariable(this.binding, this); + flowInfo.markAsDefinitelyNull(this.binding.closeTracker.binding); +// if (flowContext.initsOnFinally != null) +// flowContext.initsOnFinally.markAsDefinitelyNonNull(this.binding.closeTracker.binding); + } int nullStatus = this.initialization.nullStatus(flowInfo); if (!flowInfo.isDefinitelyAssigned(this.binding)){// for local variable debug attributes this.bits |= FirstAssignmentToLocal; Index: compiler/org/eclipse/jdt/internal/compiler/ast/MessageSend.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MessageSend.java,v retrieving revision 1.151.2.8 diff -u -r1.151.2.8 MessageSend.java --- compiler/org/eclipse/jdt/internal/compiler/ast/MessageSend.java 28 Jun 2011 14:49:04 -0000 1.151.2.8 +++ compiler/org/eclipse/jdt/internal/compiler/ast/MessageSend.java 11 Jul 2011 17:34:42 -0000 @@ -42,6 +42,7 @@ import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding; 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.ProblemSeverities; @@ -64,6 +65,20 @@ public FlowInfo analyseCode(BlockScope currentScope, FlowContext flowContext, FlowInfo flowInfo) { boolean nonStatic = !this.binding.isStatic(); flowInfo = this.receiver.analyseCode(currentScope, flowContext, flowInfo, nonStatic).unconditionalInits(); + // recording the closing of AutoCloseable resources: + if (CharOperation.equals(TypeConstants.CLOSE, this.selector)) + { + FakedTrackingVariable trackingVariable = FakedTrackingVariable.getCloseTrackingVariable(this.receiver); + if (trackingVariable != null) { // null happens if receiver is not a local variable or not an AutoCloseable + if (trackingVariable.methodScope == currentScope.methodScope()) { + flowInfo.markAsDefinitelyNonNull(trackingVariable.binding); +// if (flowContext.initsOnFinally != null) +// flowContext.initsOnFinally.markAsDefinitelyNonNull(trackingVariable.binding); + } else { + trackingVariable.closedInNestedMethod = true; + } + } + } if (nonStatic) { this.receiver.checkNPE(currentScope, flowContext, flowInfo); // https://bugs.eclipse.org/bugs/show_bug.cgi?id=318682 @@ -84,6 +99,13 @@ if ((this.arguments[i].implicitConversion & TypeIds.UNBOXING) != 0) { this.arguments[i].checkNPE(currentScope, flowContext, flowInfo); } + FakedTrackingVariable trackVar = FakedTrackingVariable.getCloseTrackingVariable(this.arguments[i]); + if (trackVar != null) { + // insert info that the tracked resource *may* be closed (by the target method, i.e.) + FlowInfo infoResourceIsClosed = flowInfo.copy(); + infoResourceIsClosed.markAsDefinitelyNonNull(trackVar.binding); + flowInfo = FlowInfo.conditional(flowInfo, infoResourceIsClosed); + } flowInfo = this.arguments[i].analyseCode(currentScope, flowContext, flowInfo).unconditionalInits(); } } Index: compiler/org/eclipse/jdt/internal/compiler/ast/MethodDeclaration.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/MethodDeclaration.java,v retrieving revision 1.79.2.1 diff -u -r1.79.2.1 MethodDeclaration.java --- compiler/org/eclipse/jdt/internal/compiler/ast/MethodDeclaration.java 5 Mar 2011 18:12:56 -0000 1.79.2.1 +++ compiler/org/eclipse/jdt/internal/compiler/ast/MethodDeclaration.java 11 Jul 2011 17:34:42 -0000 @@ -134,6 +134,7 @@ } } + this.scope.checkUnclosedCloseables(flowInfo, methodContext, null); } catch (AbortMethod e) { this.ignoreFurtherInvestigation = true; } Index: compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedAllocationExpression.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedAllocationExpression.java,v retrieving revision 1.101.2.11 diff -u -r1.101.2.11 QualifiedAllocationExpression.java --- compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedAllocationExpression.java 23 May 2011 10:57:30 -0000 1.101.2.11 +++ compiler/org/eclipse/jdt/internal/compiler/ast/QualifiedAllocationExpression.java 11 Jul 2011 17:34:42 -0000 @@ -76,6 +76,13 @@ // process arguments if (this.arguments != null) { for (int i = 0, count = this.arguments.length; i < count; i++) { + FakedTrackingVariable trackVar = FakedTrackingVariable.getCloseTrackingVariable(this.arguments[i]); + if (trackVar != null) { + // insert info that the tracked resource *may* be closed (by the target method, i.e.) + FlowInfo infoResourceIsClosed = flowInfo.copy(); + infoResourceIsClosed.markAsDefinitelyNonNull(trackVar.binding); + flowInfo = FlowInfo.conditional(flowInfo, infoResourceIsClosed); + } flowInfo = this.arguments[i].analyseCode(currentScope, flowContext, flowInfo); if ((this.arguments[i].implicitConversion & TypeIds.UNBOXING) != 0) { this.arguments[i].checkNPE(currentScope, flowContext, flowInfo); Index: compiler/org/eclipse/jdt/internal/compiler/ast/ReturnStatement.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/ReturnStatement.java,v retrieving revision 1.70.2.1 diff -u -r1.70.2.1 ReturnStatement.java --- compiler/org/eclipse/jdt/internal/compiler/ast/ReturnStatement.java 27 Apr 2011 16:33:51 -0000 1.70.2.1 +++ compiler/org/eclipse/jdt/internal/compiler/ast/ReturnStatement.java 11 Jul 2011 17:34:42 -0000 @@ -40,6 +40,13 @@ if ((this.expression.implicitConversion & TypeIds.UNBOXING) != 0) { this.expression.checkNPE(currentScope, flowContext, flowInfo); } + FakedTrackingVariable trackingVariable = FakedTrackingVariable.getCloseTrackingVariable(this.expression); + if (trackingVariable != null) { + // don't report issues concerning this local, since by returning + // the method passes the responsibility to the caller: + flowInfo.markAsDefinitelyNonNull(trackingVariable.binding); + trackingVariable.hasReportedProblem = true; + } } this.initStateIndex = currentScope.methodScope().recordInitializationStates(flowInfo); @@ -104,6 +111,7 @@ this.expression.bits |= ASTNode.IsReturnedValue; } } + currentScope.checkUnclosedCloseables(flowInfo, null/*ignore exception exits from flowContext*/, this); return FlowInfo.DEAD_END; } Index: compiler/org/eclipse/jdt/internal/compiler/ast/TryStatement.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/ast/TryStatement.java,v retrieving revision 1.116.2.26 diff -u -r1.116.2.26 TryStatement.java --- compiler/org/eclipse/jdt/internal/compiler/ast/TryStatement.java 4 Jul 2011 08:57:17 -0000 1.116.2.26 +++ compiler/org/eclipse/jdt/internal/compiler/ast/TryStatement.java 11 Jul 2011 17:34:43 -0000 @@ -15,6 +15,9 @@ *******************************************************************************/ package org.eclipse.jdt.internal.compiler.ast; +import java.util.ArrayList; +import java.util.List; + import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.ASTVisitor; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; @@ -120,8 +123,13 @@ for (int i = 0, max = this.resources.length; i < max; i++) { flowInfo = this.resources[i].analyseCode(currentScope, handlingContext, flowInfo.copy()); - this.resources[i].binding.useFlag = LocalVariableBinding.USED; // Is implicitly used anyways. - TypeBinding type = this.resources[i].binding.type; + LocalVariableBinding resourceBinding = this.resources[i].binding; + resourceBinding.useFlag = LocalVariableBinding.USED; // Is implicitly used anyways. + if (resourceBinding.closeTracker != null) { + resourceBinding.closeTracker.isInsideTryWithResources = true; + flowInfo.markAsDefinitelyNonNull(resourceBinding.closeTracker.binding); + } + TypeBinding type = resourceBinding.type; if (type != null && type.isValidBinding()) { ReferenceBinding binding = (ReferenceBinding) type; MethodBinding closeMethod = binding.getExactMethod(ConstantPool.Close, new TypeBinding [0], this.scope.compilationUnitScope()); // scope needs to be tighter @@ -227,6 +235,7 @@ this.scope.problemReporter().finallyMustCompleteNormally(this.finallyBlock); } this.subRoutineInits = subInfo; + // process the try block in a context handling the local exceptions. ExceptionHandlingFlowContext handlingContext = new ExceptionHandlingFlowContext( @@ -245,8 +254,13 @@ for (int i = 0, max = this.resources.length; i < max; i++) { flowInfo = this.resources[i].analyseCode(currentScope, handlingContext, flowInfo.copy()); - this.resources[i].binding.useFlag = LocalVariableBinding.USED; // Is implicitly used anyways. - TypeBinding type = this.resources[i].binding.type; + LocalVariableBinding resourceBinding = this.resources[i].binding; + resourceBinding.useFlag = LocalVariableBinding.USED; // Is implicitly used anyways. + if (resourceBinding.closeTracker != null) { + resourceBinding.closeTracker.isInsideTryWithResources = true; + flowInfo.markAsDefinitelyNonNull(resourceBinding.closeTracker.binding); + } + TypeBinding type = resourceBinding.type; if (type != null && type.isValidBinding()) { ReferenceBinding binding = (ReferenceBinding) type; MethodBinding closeMethod = binding.getExactMethod(ConstantPool.Close, new TypeBinding [0], this.scope.compilationUnitScope()); // scope needs to be tighter @@ -267,6 +281,13 @@ this.bits |= ASTNode.IsTryBlockExiting; } + // superimpose status for tracking variables: + // don't complain on initsOnException if finally has a better state. + List trackVars = new ArrayList(); + currentScope.getTrackVars(trackVars, subInfo); + if (trackVars.size() > 0) + flowContext.improveNullInfoForExceptionExits(trackVars, subInfo); + // check unreachable catch blocks handlingContext.complainIfUnusedExceptionHandlers(this.scope, this); Index: compiler/org/eclipse/jdt/internal/compiler/flow/ExceptionHandlingFlowContext.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/ExceptionHandlingFlowContext.java,v retrieving revision 1.45.8.6 diff -u -r1.45.8.6 ExceptionHandlingFlowContext.java --- compiler/org/eclipse/jdt/internal/compiler/flow/ExceptionHandlingFlowContext.java 18 May 2011 04:48:07 -0000 1.45.8.6 +++ compiler/org/eclipse/jdt/internal/compiler/flow/ExceptionHandlingFlowContext.java 11 Jul 2011 17:34:43 -0000 @@ -15,18 +15,21 @@ package org.eclipse.jdt.internal.compiler.flow; import java.util.ArrayList; +import java.util.List; -import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; import org.eclipse.jdt.internal.compiler.ast.Argument; -import org.eclipse.jdt.internal.compiler.ast.UnionTypeReference; +import org.eclipse.jdt.internal.compiler.ast.FakedTrackingVariable; import org.eclipse.jdt.internal.compiler.ast.SubRoutineStatement; import org.eclipse.jdt.internal.compiler.ast.TryStatement; import org.eclipse.jdt.internal.compiler.ast.TypeReference; +import org.eclipse.jdt.internal.compiler.ast.UnionTypeReference; import org.eclipse.jdt.internal.compiler.codegen.ObjectCache; import org.eclipse.jdt.internal.compiler.lookup.BlockScope; import org.eclipse.jdt.internal.compiler.lookup.CatchParameterBinding; import org.eclipse.jdt.internal.compiler.lookup.ExtraCompilerModifiers; +import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding; import org.eclipse.jdt.internal.compiler.lookup.MethodScope; import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; import org.eclipse.jdt.internal.compiler.lookup.Scope; @@ -184,7 +187,15 @@ return node; } - +public int getNullStatusAfter(LocalVariableBinding local, FlowInfo flowInfo) { + // collect info from normal flow and exceptional flows: + int status = flowInfo.nullStatus(local); + if (this.initsOnExceptions != null) + for (int i = 0; i < this.initsOnExceptions.length; i++) + status |= (this.initsOnExceptions[i].nullStatus(local) & (FlowInfo.NULL|FlowInfo.POTENTIALLY_NULL)); + // merge collected info: + return mergeNullStatus(status); +} public String individualToString() { StringBuffer buffer = new StringBuffer("Exception flow context"); //$NON-NLS-1$ @@ -302,4 +313,31 @@ } return null; } +/** + * {@inheritDoc} + */ +public void improveNullInfoForExceptionExits(List moreTrackVars, FlowInfo otherInfo) { + if (this.initsOnExceptions == null) return; + for (int j = 0; j < this.initsOnExceptions.length; j++) { + UnconditionalFlowInfo copy = null; + for (int i = 0; i < moreTrackVars.size(); i++) { + LocalVariableBinding trackVar = ((FakedTrackingVariable) moreTrackVars.get(i)).binding; + int status = otherInfo.nullStatus(trackVar); + if ((status & FlowInfo.NULL) == 0) { + if ((status & FlowInfo.NON_NULL) != 0) { + this.initsOnExceptions[j].markAsDefinitelyNonNull(trackVar); + } else if ((status & FlowInfo.POTENTIALLY_NON_NULL) != 0) { + if (this.initsOnExceptions[j].isDefinitelyNull(trackVar)) { + // cannot directly set to pot.nn, need to construct and merge infos for this task: + if (copy == null) + copy = this.initsOnExceptions[j].unconditionalCopy(); + copy.markAsDefinitelyNonNull(trackVar); + } + } + } + } + if (copy != null) + this.initsOnExceptions[j] = this.initsOnExceptions[j].mergedWith(copy); + } +} } Index: compiler/org/eclipse/jdt/internal/compiler/flow/FlowContext.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/FlowContext.java,v retrieving revision 1.68.2.8 diff -u -r1.68.2.8 FlowContext.java --- compiler/org/eclipse/jdt/internal/compiler/flow/FlowContext.java 27 Jun 2011 11:58:08 -0000 1.68.2.8 +++ compiler/org/eclipse/jdt/internal/compiler/flow/FlowContext.java 11 Jul 2011 17:34:44 -0000 @@ -15,9 +15,11 @@ package org.eclipse.jdt.internal.compiler.flow; import java.util.ArrayList; +import java.util.List; + import org.eclipse.jdt.core.compiler.CharOperation; -import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; import org.eclipse.jdt.internal.compiler.ast.Expression; import org.eclipse.jdt.internal.compiler.ast.LabeledStatement; import org.eclipse.jdt.internal.compiler.ast.Reference; @@ -395,6 +397,14 @@ return null; } +/** + * Answer the combined null status that local will have after this flow context. + * Subclasses will respect break and exception exits. + */ +public int getNullStatusAfter(LocalVariableBinding local, FlowInfo flowInfo) { + return flowInfo.nullStatus(local); +} + /* * lookup through break labels */ @@ -736,4 +746,40 @@ buffer.append(individualToString()).append('\n'); return buffer.toString(); } +/** + * For all tracking variables in the given list improve the null info in initsOnException (if existent) + * with information from the given otherInfo. + */ +public void improveNullInfoForExceptionExits(List moreTrackVars, FlowInfo otherInfo) { + // nothing here, overridden in ExceptionHandlingFlowContext +} + +/** + * Get the null status that local will have after the current point as indicated by flowInfo and flowContext + * @param local + * @param flowInfo + * @param flowContext may be null + * @return a bitset from the constants FlowInfo.{NULL,POTENTIALLY_NULL,POTENTIALLY_NON_NULL,NON_NULL}. + */ +public static int getNullStatusAfter(LocalVariableBinding local, FlowInfo flowInfo, FlowContext flowContext) { + int status = (flowContext != null) + ? flowContext.getNullStatusAfter(local, flowInfo) + : flowInfo.nullStatus(local); + return mergeNullStatus(status); +} + +/** Merge the bits NULL, NON_NULL, POTENTIALLY_NULL, POTENTIALLY_NON_NULL to a one-bit answer. */ +protected static int mergeNullStatus(int status) { + if ((status & FlowInfo.NULL) != 0) { + if ((status & (FlowInfo.NON_NULL | FlowInfo.POTENTIALLY_NON_NULL)) != 0) + return FlowInfo.POTENTIALLY_NULL; // null + doubt = pot null + return FlowInfo.NULL; + } else if ((status & FlowInfo.NON_NULL) != 0) { + if ((status & FlowInfo.POTENTIALLY_NULL) != 0) + return FlowInfo.POTENTIALLY_NULL; // non-null + doubt = pot null + return FlowInfo.NON_NULL; + } else if ((status & FlowInfo.POTENTIALLY_NULL) != 0) + return FlowInfo.POTENTIALLY_NULL; + return status; +} } Index: compiler/org/eclipse/jdt/internal/compiler/flow/SwitchFlowContext.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/SwitchFlowContext.java,v retrieving revision 1.32.8.1 diff -u -r1.32.8.1 SwitchFlowContext.java --- compiler/org/eclipse/jdt/internal/compiler/flow/SwitchFlowContext.java 8 Mar 2011 17:20:08 -0000 1.32.8.1 +++ compiler/org/eclipse/jdt/internal/compiler/flow/SwitchFlowContext.java 11 Jul 2011 17:34:44 -0000 @@ -12,6 +12,7 @@ import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.codegen.BranchLabel; +import org.eclipse.jdt.internal.compiler.lookup.LocalVariableBinding; /** * Reflects the context of code analysis, keeping track of enclosing @@ -31,6 +32,15 @@ return this.breakLabel; } +public int getNullStatusAfter(LocalVariableBinding local, FlowInfo flowInfo) { + // collect info from normal flow and breaks flows: + int status = flowInfo.nullStatus(local); + if (this.initsOnBreak != null) + status |= this.initsOnBreak.nullStatus(local); + // merge collected info: + return mergeNullStatus(status); +} + public String individualToString() { StringBuffer buffer = new StringBuffer("Switch flow context"); //$NON-NLS-1$ buffer.append("[initsOnBreak -").append(this.initsOnBreak.toString()).append(']'); //$NON-NLS-1$ Index: compiler/org/eclipse/jdt/internal/compiler/flow/UnconditionalFlowInfo.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/flow/UnconditionalFlowInfo.java,v retrieving revision 1.72.2.3 diff -u -r1.72.2.3 UnconditionalFlowInfo.java --- compiler/org/eclipse/jdt/internal/compiler/flow/UnconditionalFlowInfo.java 12 Apr 2011 08:35:58 -0000 1.72.2.3 +++ compiler/org/eclipse/jdt/internal/compiler/flow/UnconditionalFlowInfo.java 11 Jul 2011 17:34:45 -0000 @@ -770,7 +770,7 @@ } int vectorIndex; if ((vectorIndex = (position / BitCacheSize) - 1) - >= this.extra[0].length) { + >= this.extra[2].length) { return false; // if not enough room in vector, then not initialized } return ((this.extra[2][vectorIndex] & this.extra[4][vectorIndex] @@ -797,7 +797,7 @@ } int vectorIndex; if ((vectorIndex = (position / BitCacheSize) - 1) >= - this.extra[0].length) { + this.extra[2].length) { return false; // if not enough room in vector, then not initialized } return ((this.extra[2][vectorIndex] & this.extra[3][vectorIndex] @@ -822,7 +822,7 @@ } int vectorIndex; if ((vectorIndex = (position / BitCacheSize) - 1) >= - this.extra[0].length) { + this.extra[2].length) { return false; // if not enough room in vector, then not initialized } return ((this.extra[2][vectorIndex] & this.extra[5][vectorIndex] @@ -882,7 +882,7 @@ } int vectorIndex; if ((vectorIndex = (position / BitCacheSize) - 1) >= - this.extra[0].length) { + this.extra[2].length) { return false; // if not enough room in vector, then not initialized } return ((this.extra[4][vectorIndex] @@ -908,7 +908,7 @@ } int vectorIndex; if ((vectorIndex = (position / BitCacheSize) - 1) >= - this.extra[0].length) { + this.extra[2].length) { return false; // if not enough room in vector, then not initialized } return ((this.extra[3][vectorIndex] @@ -934,7 +934,7 @@ } int vectorIndex; if ((vectorIndex = (position / BitCacheSize) - 1) >= - this.extra[0].length) { + this.extra[2].length) { return false; // if not enough room in vector, then not initialized } return (this.extra[5][vectorIndex] @@ -1889,7 +1889,6 @@ } return count; } - public UnconditionalFlowInfo nullInfoLessUnconditionalCopy() { if (this == DEAD_END) { return this; Index: compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java,v retrieving revision 1.241.2.3 diff -u -r1.241.2.3 CompilerOptions.java --- compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java 14 Feb 2011 14:08:01 -0000 1.241.2.3 +++ compiler/org/eclipse/jdt/internal/compiler/impl/CompilerOptions.java 11 Jul 2011 17:34:47 -0000 @@ -136,6 +136,9 @@ public static final String OPTION_IncludeNullInfoFromAsserts = "org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts"; //$NON-NLS-1$ public static final String OPTION_ReportMethodCanBeStatic = "org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic"; //$NON-NLS-1$ public static final String OPTION_ReportMethodCanBePotentiallyStatic = "org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic"; //$NON-NLS-1$ + public static final String OPTION_ReportUnclosedCloseable = "org.eclipse.jdt.core.compiler.problem.unclosedCloseable"; //$NON-NLS-1$ + public static final String OPTION_ReportPotentiallyUnclosedCloseable = "org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable"; //$NON-NLS-1$ + public static final String OPTION_ReportExplicitlyClosedAutoCloseable = "org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable"; //$NON-NLS-1$ /** * Possible values for configurable options */ @@ -238,6 +241,9 @@ public static final int UnusedObjectAllocation = IrritantSet.GROUP2 | ASTNode.Bit4; public static final int MethodCanBeStatic = IrritantSet.GROUP2 | ASTNode.Bit5; public static final int MethodCanBePotentiallyStatic = IrritantSet.GROUP2 | ASTNode.Bit6; + public static final int UnclosedCloseable = IrritantSet.GROUP2 | ASTNode.Bit7; + public static final int PotentiallyUnclosedCloseable = IrritantSet.GROUP2 | ASTNode.Bit8; + public static final int ExplicitlyClosedAutoCloseable = IrritantSet.GROUP2 | ASTNode.Bit9; // Severity level for handlers /** @@ -547,6 +553,12 @@ return OPTION_ReportMethodCanBeStatic; case MethodCanBePotentiallyStatic : return OPTION_ReportMethodCanBePotentiallyStatic; + case UnclosedCloseable : + return OPTION_ReportUnclosedCloseable; + case PotentiallyUnclosedCloseable : + return OPTION_ReportPotentiallyUnclosedCloseable; + case ExplicitlyClosedAutoCloseable : + return OPTION_ReportExplicitlyClosedAutoCloseable; } return null; } @@ -709,6 +721,9 @@ OPTION_ReportUnusedTypeArgumentsForMethodInvocation, OPTION_ReportUnusedWarningToken, OPTION_ReportVarargsArgumentNeedCast, + OPTION_ReportUnclosedCloseable, + OPTION_ReportPotentiallyUnclosedCloseable, + OPTION_ReportExplicitlyClosedAutoCloseable, }; return result; } @@ -973,6 +988,9 @@ optionsMap.put(OPTION_IncludeNullInfoFromAsserts, this.includeNullInfoFromAsserts ? ENABLED : DISABLED); optionsMap.put(OPTION_ReportMethodCanBeStatic, getSeverityString(MethodCanBeStatic)); optionsMap.put(OPTION_ReportMethodCanBePotentiallyStatic, getSeverityString(MethodCanBePotentiallyStatic)); + optionsMap.put(OPTION_ReportUnclosedCloseable, getSeverityString(UnclosedCloseable)); + optionsMap.put(OPTION_ReportPotentiallyUnclosedCloseable, getSeverityString(PotentiallyUnclosedCloseable)); + optionsMap.put(OPTION_ReportExplicitlyClosedAutoCloseable, getSeverityString(ExplicitlyClosedAutoCloseable)); return optionsMap; } @@ -1402,6 +1420,9 @@ if ((optionValue = optionsMap.get(OPTION_ReportUnusedObjectAllocation)) != null) updateSeverity(UnusedObjectAllocation, optionValue); if ((optionValue = optionsMap.get(OPTION_ReportMethodCanBeStatic)) != null) updateSeverity(MethodCanBeStatic, optionValue); if ((optionValue = optionsMap.get(OPTION_ReportMethodCanBePotentiallyStatic)) != null) updateSeverity(MethodCanBePotentiallyStatic, optionValue); + if ((optionValue = optionsMap.get(OPTION_ReportUnclosedCloseable)) != null) updateSeverity(UnclosedCloseable, optionValue); + if ((optionValue = optionsMap.get(OPTION_ReportPotentiallyUnclosedCloseable)) != null) updateSeverity(PotentiallyUnclosedCloseable, optionValue); + if ((optionValue = optionsMap.get(OPTION_ReportExplicitlyClosedAutoCloseable)) != null) updateSeverity(ExplicitlyClosedAutoCloseable, optionValue); // Javadoc options if ((optionValue = optionsMap.get(OPTION_DocCommentSupport)) != null) { Index: compiler/org/eclipse/jdt/internal/compiler/impl/IrritantSet.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/impl/IrritantSet.java,v retrieving revision 1.13.2.1 diff -u -r1.13.2.1 IrritantSet.java --- compiler/org/eclipse/jdt/internal/compiler/impl/IrritantSet.java 11 Feb 2011 15:17:16 -0000 1.13.2.1 +++ compiler/org/eclipse/jdt/internal/compiler/impl/IrritantSet.java 11 Jul 2011 17:34:47 -0000 @@ -99,7 +99,8 @@ // group-2 warnings enabled by default .set( CompilerOptions.DeadCode - |CompilerOptions.Tasks); + |CompilerOptions.Tasks + |CompilerOptions.UnclosedCloseable); ALL.setAll(); HIDING Index: compiler/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java,v retrieving revision 1.136 diff -u -r1.136 BinaryTypeBinding.java --- compiler/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java 17 Jan 2011 13:00:56 -0000 1.136 +++ compiler/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java 11 Jul 2011 17:34:47 -0000 @@ -971,6 +971,12 @@ variable.resolve(); return variable; } +public boolean hasTypeBit(int bit) { + // ensure hierarchy is resolved + superclass(); + superInterfaces(); + return (this.typeBits & bit) != 0; +} private void initializeTypeVariable(TypeVariableBinding variable, TypeVariableBinding[] existingVariables, SignatureWrapper wrapper, char[][][] missingTypeNames) { // ParameterSignature = Identifier ':' TypeSignature // or Identifier ':' TypeSignature(optional) InterfaceBound(s) @@ -1139,8 +1145,13 @@ // finish resolving the type this.superclass = (ReferenceBinding) resolveType(this.superclass, this.environment, true /* raw conversion */); this.tagBits &= ~TagBits.HasUnresolvedSuperclass; - if (this.superclass.problemId() == ProblemReasons.NotFound) + if (this.superclass.problemId() == ProblemReasons.NotFound) { this.tagBits |= TagBits.HierarchyHasProblems; // propagate type inconsistency + } else { + this.superclass.superclass(); + this.superclass.superInterfaces(); + } + this.typeBits |= this.superclass.typeBits; return this.superclass; } // NOTE: superInterfaces of binary types are resolved when needed @@ -1150,8 +1161,13 @@ for (int i = this.superInterfaces.length; --i >= 0;) { this.superInterfaces[i] = (ReferenceBinding) resolveType(this.superInterfaces[i], this.environment, true /* raw conversion */); - if (this.superInterfaces[i].problemId() == ProblemReasons.NotFound) + if (this.superInterfaces[i].problemId() == ProblemReasons.NotFound) { this.tagBits |= TagBits.HierarchyHasProblems; // propagate type inconsistency + } else { + this.superInterfaces[i].superclass(); + this.superInterfaces[i].superInterfaces(); + } + this.typeBits |= this.superInterfaces[i].typeBits; } this.tagBits &= ~TagBits.HasUnresolvedSuperinterfaces; return this.superInterfaces; Index: compiler/org/eclipse/jdt/internal/compiler/lookup/BlockScope.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BlockScope.java,v retrieving revision 1.123.2.3 diff -u -r1.123.2.3 BlockScope.java --- compiler/org/eclipse/jdt/internal/compiler/lookup/BlockScope.java 5 May 2011 14:31:10 -0000 1.123.2.3 +++ compiler/org/eclipse/jdt/internal/compiler/lookup/BlockScope.java 11 Jul 2011 17:34:48 -0000 @@ -10,10 +10,15 @@ *******************************************************************************/ package org.eclipse.jdt.internal.compiler.lookup; +import java.util.ArrayList; +import java.util.List; + import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.ast.*; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.codegen.CodeStream; +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.problem.ProblemReporter; @@ -957,4 +962,53 @@ } } } + +private List trackingVariables; +public int registerTrackingVariable(FakedTrackingVariable fakedTrackingVariable) { + if (this.trackingVariables == null) + this.trackingVariables = new ArrayList(); + this.trackingVariables.add(fakedTrackingVariable); + MethodScope outerMethodScope = outerMostMethodScope(); + return outerMethodScope.analysisIndex + (++outerMethodScope.trackVarCount); + +} +public void checkUnclosedCloseables(FlowInfo flowInfo, FlowContext flowContext, ASTNode location) { + if (this.trackingVariables == null) return; + for (int i=0; i= ClassFileConstants.JDK1_7 + && !trackingVar.isInsideTryWithResources) + problemReporter().explicitlyClosedAutoCloseable(trackingVar); + } + } +} +/** + * Find all tracking variables in scope that have relevant null status. + * @param moreTrackVars list for collecting all found tracking variables + * @param flowInfo use to check if tracking variable has interesting status + */ +public void getTrackVars(List moreTrackVars, FlowInfo flowInfo) { + if (this.trackingVariables == null) return; + for (int i=0; iWhen enabled, that compiler will issue an error or a warning if a local variable + * holds a value of type AutoCloseable (or Closeable if compliance is < 1.7) + * and if flow analysis shows that the method close() is not invoked locally on that value. + *
+ *
Option id:
"org.eclipse.jdt.core.compiler.problem.reportUnclosedCloseable"
+ *
Possible values:
{ "error", "warning", "ignore" }
+ *
Default:
"warning"
+ *
+ * @since 3.7 + * @category CompilerOptionID + */ + public static final String COMPILER_PB_UNCLOSED_CLOSEABLE = PLUGIN_ID + ".compiler.problem.unclosedCloseable"; //$NON-NLS-1$ + /** + * Compiler option ID: Reporting a resource that may not be closed properly. + *

When enabled, that compiler will issue an error or a warning if a local variable + * holds a value of type AutoCloseable (or Closeable if compliance is < 1.7) + * and if flow analysis shows that the method close() is not invoked locally + * on that value for all execution paths. + *

+ *
Option id:
"org.eclipse.jdt.core.compiler.problem.reportPotentiallyUnclosedCloseable"
+ *
Possible values:
{ "error", "warning", "ignore" }
+ *
Default:
"ignore"
+ *
+ * @since 3.7 + * @category CompilerOptionID + */ + public static final String COMPILER_PB_POTENTIALLY_UNCLOSED_CLOSEABLE = PLUGIN_ID + ".compiler.problem.potentiallyUnclosedCloseable"; //$NON-NLS-1$ + /** + * Compiler option ID: Reporting a resource that is not managed by try-with-resources. + *

When enabled, that compiler will issue an error or a warning if a local variable + * holds a value of type AutoCloseable, and if the method close() is + * explicitly invoked on that resource, but the resource is not managed by a + * try-with-resources block. + *

Note that this option is not surfaced in the UI, as it is intended only for internal + * use for computing quick assists / cleanups. + *

+ *
Option id:
"org.eclipse.jdt.core.compiler.problem.reportPotentiallyUnclosedCloseable"
+ *
Possible values:
{ "error", "warning", "ignore" }
+ *
Default:
"ignore"
+ *
+ * @since 3.7 + * @category CompilerOptionID + */ + public static final String COMPILER_PB_EXPLICITLY_CLOSED_AUTOCLOSEABLE = PLUGIN_ID + ".compiler.problem.explicitlyClosedAutoCloseable"; //$NON-NLS-1$ + /** * Compiler option ID: Setting Source Compatibility Mode. *

Specify whether which source level compatibility is used. From 1.4 on, 'assert' is a keyword * reserved for assertion support. Also note, than when toggling to 1.4 mode, the target VM #P org.eclipse.jdt.core.tests.compiler Index: src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest.java,v retrieving revision 1.224.2.2 diff -u -r1.224.2.2 BatchCompilerTest.java --- src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest.java 28 Jun 2011 19:34:33 -0000 1.224.2.2 +++ src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest.java 11 Jul 2011 17:35:10 -0000 @@ -1816,6 +1816,7 @@ "