Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Model try-with-resources semantics during CFG construction #6273

Merged
merged 65 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from 60 commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
277e50d
WIP
msridhar Oct 28, 2023
223d652
docs
msridhar Oct 28, 2023
ead8dfc
Merge branch 'master' into resource-node
msridhar Oct 29, 2023
c05d2ca
tweak test
msridhar Oct 29, 2023
1615e4c
fix nullness issue
msridhar Oct 29, 2023
a87f430
rename
msridhar Oct 29, 2023
cdb2911
Merge branch 'master' into resource-node
msridhar Oct 30, 2023
093119e
final field test
msridhar Oct 30, 2023
9cd05cb
more tests
msridhar Oct 30, 2023
0a114f0
Merge branch 'master' into resource-node
msridhar Oct 30, 2023
f0d9290
Merge branch 'master' into resource-node
msridhar Nov 1, 2023
dbef16b
Merge branch 'master' into resource-node
msridhar Nov 3, 2023
eb4492f
WIP
msridhar Nov 3, 2023
b1c7fd3
tweak comment
msridhar Nov 3, 2023
309bb77
initial fix
msridhar Nov 3, 2023
caebcff
test case showing false positive
msridhar Nov 3, 2023
efb8ff0
Merge branch 'master' into resource-node
msridhar Nov 4, 2023
f2f95ce
another test
msridhar Nov 4, 2023
8db28dd
Merge branch 'master' into resource-node
msridhar Nov 7, 2023
db8b95a
WIP, still buggy
msridhar Nov 7, 2023
ecaf99c
WIP
msridhar Nov 7, 2023
df9878a
fix bug
msridhar Nov 8, 2023
6591f31
fix test
msridhar Nov 8, 2023
3213797
javadoc
msridhar Nov 8, 2023
ff08d75
reduced test case
msridhar Nov 8, 2023
2235a3f
initial fix
msridhar Nov 8, 2023
3365594
better fix
msridhar Nov 8, 2023
a500096
fix ordering, still needs significant cleanup
msridhar Nov 9, 2023
b296327
javadoc
msridhar Nov 9, 2023
f935a72
Merge branch 'master' into resource-node
msridhar Nov 9, 2023
d55ebdb
delete code
msridhar Nov 9, 2023
d7c8246
add todos
msridhar Nov 9, 2023
83743ad
delete more code
msridhar Nov 10, 2023
da8190c
refactoring
msridhar Nov 10, 2023
a9b5686
move method
msridhar Nov 10, 2023
e6f09c2
more cleanup
msridhar Nov 10, 2023
0537858
try not using ResourceCloseNodes
msridhar Nov 10, 2023
6baaea0
Further cleanup
msridhar Nov 10, 2023
8799f5f
test with two owning fields
msridhar Nov 10, 2023
8834a88
better docs
msridhar Nov 10, 2023
86ccb54
fix tags in javadoc
msridhar Nov 10, 2023
21db3e1
Merge branch 'master' into resource-node
msridhar Nov 12, 2023
d3debc1
Handle exceptions thrown by resource close call
msridhar Nov 12, 2023
9c58a0c
Minor cleanups
msridhar Nov 12, 2023
5d77a3c
Merge branch 'master' into resource-node
msridhar Nov 12, 2023
2f7813d
Merge branch 'master' into resource-node
msridhar Nov 14, 2023
fd8683c
note that errors are false positives
msridhar Nov 14, 2023
4645b32
update javadoc
msridhar Nov 14, 2023
492d022
Merge branch 'master' into resource-node
msridhar Nov 16, 2023
47a207b
Merge branch 'master' into resource-node
msridhar Nov 16, 2023
30ef450
Merge branch 'master' into resource-node
msridhar Nov 20, 2023
eb1fb7a
Merge branch 'master' into resource-node
msridhar Nov 22, 2023
8d7ecdf
Merge branch 'master' into resource-node
msridhar Nov 24, 2023
8e7a5d8
Merge ../checker-framework-branch-master into resource-node
mernst Nov 27, 2023
4aad9ff
Tweak comments
mernst Nov 27, 2023
6c85dc8
Tweak comments
mernst Nov 27, 2023
353ac6f
Tweak comments
mernst Nov 27, 2023
c0c0b81
address some comments
msridhar Nov 28, 2023
76d5e39
clarify
msridhar Nov 28, 2023
23329e4
Comment tweaks
mernst Nov 28, 2023
be5b98e
rename method
msridhar Nov 28, 2023
586e43c
Merge branch 'master' into resource-node
msridhar Nov 28, 2023
4589b0d
tweak comment
msridhar Nov 28, 2023
9d4ed3b
Merge branch 'master' into resource-node
msridhar Nov 29, 2023
01d9948
Merge branch 'master' into resource-node
msridhar Nov 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -515,9 +515,6 @@ public Void visitIdentifier(IdentifierTree tree, AnnotatedTypeMirror type) {
type.replaceAnnotation(BOTTOM);
}
}
if (ElementUtils.isResourceVariable(elt)) {
type.replaceAnnotation(withoutClose(type.getPrimaryAnnotationInHierarchy(TOP)));
}
return super.visitIdentifier(tree, type);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,11 @@
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.checker.mustcall.qual.MustCall;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.resourceleak.ResourceLeakChecker;
import org.checkerframework.dataflow.analysis.TransferInput;
import org.checkerframework.dataflow.analysis.TransferResult;
import org.checkerframework.dataflow.cfg.node.AssignmentNode;
import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
import org.checkerframework.dataflow.cfg.node.Node;
Expand All @@ -31,7 +29,6 @@
import org.checkerframework.framework.flow.CFStore;
import org.checkerframework.framework.flow.CFTransfer;
import org.checkerframework.framework.flow.CFValue;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.TreePathUtil;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;
Expand Down Expand Up @@ -114,28 +111,6 @@ private AnnotationMirror getDefaultStringType(StringConversionNode n) {
return this.defaultStringType;
}

@Override
public TransferResult<CFValue, CFStore> visitAssignment(
AssignmentNode n, TransferInput<CFValue, CFStore> in) {
TransferResult<CFValue, CFStore> result = super.visitAssignment(n, in);
// Remove "close" from the type in the store for resource variables.
// The Resource Leak Checker relies on this code to avoid checking that
// resource variables are closed.
if (ElementUtils.isResourceVariable(TreeUtils.elementFromTree(n.getTarget().getTree()))) {
CFStore store = result.getRegularStore();
JavaExpression expr = JavaExpression.fromNode(n.getTarget());
CFValue value = store.getValue(expr);
AnnotationMirror withClose =
atypeFactory.getAnnotationByClass(value.getAnnotations(), MustCall.class);
if (withClose == null) {
return result;
}
AnnotationMirror withoutClose = atypeFactory.withoutClose(withClose);
insertIntoStores(result, expr, withoutClose);
}
return result;
}

@Override
public TransferResult<CFValue, CFStore> visitMethodInvocation(
MethodInvocationNode n, TransferInput<CFValue, CFStore> in) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
import org.checkerframework.checker.mustcall.qual.InheritableMustCall;
import org.checkerframework.checker.mustcall.qual.MustCall;
import org.checkerframework.checker.mustcall.qual.MustCallAlias;
Expand Down Expand Up @@ -267,75 +266,6 @@ protected boolean skipReceiverSubtypeCheck(
return true;
}

/**
* This boolean is used to communicate between different levels of the common assignment check
* whether a given check is being carried out on a (pseudo-)assignment to a resource variable. In
* those cases, close doesn't need to be considered when doing the check, since close will always
* be called by Java.
*
* <p>The check for whether the LHS is a resource variable can only be carried out on the element,
* but the effect needs to happen at the stage where the type is available (i.e. close needs to be
* removed from the type). Thus, this variable is used to communicate that a resource variable was
* detected on the LHS.
*/
private boolean commonAssignmentCheckOnResourceVariable = false;

/**
* {@inheritDoc}
*
* <p>Mark (using the {@code #commonAssignmentCheckOnResourceVariable} field of this class) any
* assignments where the LHS is a resource variable, so that close doesn't need to be considered.
* See {@link #commonAssignmentCheck(AnnotatedTypeMirror, AnnotatedTypeMirror, Tree, String,
* Object...)} for the code that uses and removes the mark.
*/
@Override
protected boolean commonAssignmentCheck(
Tree varTree,
ExpressionTree valueExp,
@CompilerMessageKey String errorKey,
Object... extraArgs) {
Element elt = TreeUtils.elementFromTree(varTree);
if (elt != null && elt.getKind() == ElementKind.RESOURCE_VARIABLE) {
commonAssignmentCheckOnResourceVariable = true;
}
return super.commonAssignmentCheck(varTree, valueExp, errorKey, extraArgs);
}

/**
* {@inheritDoc}
*
* <p>Iff the LHS is a resource variable, then {@code #commonAssignmentCheckOnResourceVariable}
* will be true. This method guarantees that {@code #commonAssignmentCheckOnResourceVariable} will
* be false when it returns.
*/
@Override
protected boolean commonAssignmentCheck(
AnnotatedTypeMirror varType,
AnnotatedTypeMirror valueType,
Tree valueTree,
@CompilerMessageKey String errorKey,
Object... extraArgs) {

if (commonAssignmentCheckOnResourceVariable) {
commonAssignmentCheckOnResourceVariable = false;
// The LHS has been marked as a resource variable. Skip the standard common assignment
// check; instead do a check that does not include "close".
AnnotationMirror varAnno = varType.getPrimaryAnnotationInHierarchy(atypeFactory.TOP);
AnnotationMirror valueAnno = valueType.getPrimaryAnnotationInHierarchy(atypeFactory.TOP);
if (qualHierarchy.isSubtypeShallow(
atypeFactory.withoutClose(valueAnno),
valueType.getUnderlyingType(),
atypeFactory.withoutClose(varAnno),
varType.getUnderlyingType())) {
return true;
}
// Note that in this case, the rest of the common assignment check should fail (barring
// an exception). Control falls through here to avoid duplicating error-issuing code.
}
// commonAssignmentCheckOnResourceVariable is already false, so no need to set it.
return super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs);
}

/**
* This method typically issues a warning if the result type of the constructor is not top,
* because in top-default type systems that indicates a potential problem. The Must Call Checker
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@
import org.checkerframework.dataflow.util.NodeUtils;
import org.checkerframework.framework.flow.CFStore;
import org.checkerframework.framework.flow.CFValue;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.util.JavaExpressionParseUtil.JavaExpressionParseException;
import org.checkerframework.framework.util.StringToJavaExpression;
import org.checkerframework.javacutil.AnnotationUtils;
Expand Down Expand Up @@ -711,8 +710,7 @@ private void checkCreatesMustCallForInvocation(
* <li>2) the expression already has a tracked Obligation (i.e. there is already a resource
* alias in some Obligation's resource alias set that refers to the expression), or
* <li>3) the method in which the invocation occurs also has an @CreatesMustCallFor annotation,
* with the same expression, or
* <li>4) the expression is a resource variable.
* with the same expression.
* </ul>
*
* @param obligations the currently-tracked Obligations; this value is side-effected if there is
Expand All @@ -739,10 +737,6 @@ private boolean isValidCreatesMustCallForExpression(
// formal parameters are also represented by LocalVariable in the bodies of methods.
// This satisfies case 1.
return true;
} else if (ElementUtils.isResourceVariable(elt)) {
// The expression is a resource variable, and therefore will be closed, so we can
// treat it as owning (case 4).
return true;
} else {
Obligation toRemove = null;
Obligation toAdd = null;
Expand Down Expand Up @@ -1224,16 +1218,6 @@ private void updateObligationsForAssignment(
new ResourceAlias(JavaExpression.fromNode(lhs), lhsElement, lhs.getTree()));
}
}
} else if (lhsElement.getKind() == ElementKind.RESOURCE_VARIABLE && isMustCallClose(rhs)) {
if (rhs instanceof FieldAccessNode) {
// Handling of @Owning fields is a completely separate check.
} else if (rhs instanceof LocalVariableNode) {
removeObligationsContainingVar(
obligations,
(LocalVariableNode) rhs,
MustCallAliasHandling.RETAIN_OBLIGATIONS_DERIVED_FROM_A_MUST_CALL_ALIAS_PARAMETER,
MethodExitKind.ALL);
}
} else if (lhs instanceof LocalVariableNode) {
LocalVariableNode lhsVar = (LocalVariableNode) lhs;
updateObligationsForPseudoAssignment(obligations, assignmentNode, lhsVar, rhs);
Expand Down Expand Up @@ -1264,22 +1248,6 @@ private boolean hasAtMostOneOwningField(TypeElement element) {
return true;
}

/**
* Returns true if must-call type of node only contains close. This is a helper method for
* handling try-with-resources statements.
*
* @param node the node
* @return true if must-call type of node only contains close
*/
/*package-private*/ boolean isMustCallClose(Node node) {
MustCallAnnotatedTypeFactory mcAtf =
typeFactory.getTypeFactoryOfSubchecker(MustCallChecker.class);
AnnotatedTypeMirror mustCallAnnotatedType = mcAtf.getAnnotatedType(node.getTree());
AnnotationMirror mustCallAnnotation =
mustCallAnnotatedType.getPrimaryAnnotation(MustCall.class);
return typeFactory.getMustCallValues(mcAtf.withoutClose(mustCallAnnotation)).isEmpty();
}

/**
* Add a new alias to all Obligations that have {@code var} in their resource-alias set. This
* method should be used when {@code var} and {@code newAlias} definitively point to the same
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,8 +369,6 @@ private void analyzeOwnershipTransferAtAssignment(
disposedFields.remove((VariableElement) lhsElement);
}
addOwningToParamsIfDisposedAtAssignment(obligations, rhsObligation, rhs);
} else if (lhsElement.getKind() == ElementKind.RESOURCE_VARIABLE && mcca.isMustCallClose(rhs)) {
addOwningToParamsIfDisposedAtAssignment(obligations, rhsObligation, rhs);
} else if (lhs instanceof LocalVariableNode) {
LocalVariableNode lhsVar = (LocalVariableNode) lhs;
mcca.updateObligationsForPseudoAssignment(obligations, assignmentNode, lhsVar, rhs);
Expand Down
12 changes: 6 additions & 6 deletions checker/tests/mustcall/TryWithResourcesSimple.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// A test that try-with-resources variables are always @MustCall({}).
// A test that try-with-resources variables are always @MustCall({"close"}).

import java.io.*;
import java.net.*;
Expand All @@ -7,7 +7,7 @@
public class TryWithResourcesSimple {
static void test(String address, int port) {
try (Socket socket = new Socket(address, port)) {
@MustCall({}) Object s = socket;
@MustCall({"close"}) Object s = socket;
} catch (Exception e) {

}
Expand All @@ -23,16 +23,16 @@ void test_fancy_sock(String address, int port) {
// "close", which is the only MC method for Socket itself.
try (Socket socket = getFancySocket()) {
// :: error: (assignment)
@MustCall({}) Object s = socket;
@MustCall({"close"}) Object s = socket;
} catch (Exception e) {

}
}

static void test_poly(String address, int port) {
try (Socket socket = new Socket(address, port)) {
// getChannel is @MustCallAlias (= poly) with the socket, so it should also be @MC({})
@MustCall({}) Object s = socket.getChannel();
// getChannel is @MustCallAlias (= poly) with the socket, so it should also be @MC({"close"})
@MustCall({"close"}) Object s = socket.getChannel();
} catch (Exception e) {

}
Expand All @@ -41,7 +41,7 @@ static void test_poly(String address, int port) {
static void test_two_mca_variables(String address, int port) {
try (Socket socket = new Socket(address, port);
InputStream in = socket.getInputStream()) {
@MustCall({}) Object s = in;
@MustCall({"close"}) Object s = in;
} catch (Exception e) {

}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// A simple test that a try-with-resources socket doesn't issue a false positive.
// Tests for try-with-resources that declare local variables in the resource declaration.

import java.io.*;
import java.net.Socket;
import java.nio.channels.*;
import java.util.*;

class TryWithResourcesSimple {
class TryWithResourcesDeclaration {
static void test(String address, int port) {
try (Socket socket = new Socket(address, port)) {

Expand All @@ -31,4 +31,13 @@ public boolean isPreUpgradableLayout(File oldF) throws IOException {
return false;
}
}

public void testNestedTryWithResourcesDecls(Properties prop, ClassLoader cl, String propfile)
throws Exception {
try (InputStream in = cl.getResourceAsStream(propfile)) {
try (InputStream fis = new FileInputStream(propfile)) {
prop.load(fis);
}
}
}
}
Loading