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

@CreatesMustCallFor create an obligation on exceptional successors #6221

Open
wants to merge 44 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
30fda21
update
Nargeshdb Aug 25, 2023
79d248d
update
Nargeshdb Oct 2, 2023
b5e6f13
fix the 6050 issue
Nargeshdb Oct 3, 2023
120fbca
update
Nargeshdb Oct 3, 2023
c940e76
fix
Nargeshdb Oct 4, 2023
663fde3
Merge remote-tracking branch 'origin' into FN-CreatesMustCallFor
Nargeshdb Oct 4, 2023
24fcd0b
add doc
Nargeshdb Oct 4, 2023
3f41584
update
Nargeshdb Oct 4, 2023
8d28581
trying to repro Daikon failure
msridhar Oct 5, 2023
5da7fc5
Merge remote-tracking branch 'origin' into FN-CreatesMustCallFor
Nargeshdb Oct 5, 2023
89ff874
Merge branch 'master' into FN-CreatesMustCallFor
Nargeshdb Oct 5, 2023
4f0aa0f
add a test to repo the issue
Nargeshdb Oct 7, 2023
bab4bc8
repo the issue
Nargeshdb Oct 9, 2023
b12962a
Merge remote-tracking branch 'origin/master' into FN-CreatesMustCallFor
smillst Oct 10, 2023
9d82755
Use util method.
smillst Oct 10, 2023
8a832d3
Merge branch 'master' into FN-CreatesMustCallFor
Nargeshdb Oct 25, 2023
937af32
reformat
Nargeshdb Oct 25, 2023
535ae4e
Merge branch 'master' into FN-CreatesMustCallFor
Nargeshdb Oct 25, 2023
bf3f775
revert changes
Nargeshdb Oct 26, 2023
9bcc73c
remove extra test
Nargeshdb Oct 30, 2023
13b080d
Merge branch 'master' into FN-CreatesMustCallFor
Nargeshdb Oct 30, 2023
2c93562
reformat
Nargeshdb Oct 30, 2023
7f5a8a1
check declared exception types
Nargeshdb Oct 31, 2023
8282b7c
Merge branch 'master' into FN-CreatesMustCallFor
Nargeshdb Oct 31, 2023
66da755
check delared exception types
Nargeshdb Nov 1, 2023
647d669
fix
Nargeshdb Nov 1, 2023
c3674df
Merge remote-tracking branch 'origin' into FN-CreatesMustCallFor
Nargeshdb Nov 2, 2023
9a5a614
ignored exception in mc-typefactory
Nargeshdb Nov 2, 2023
74cbcef
Merge branch 'master' into FN-CreatesMustCallFor
msridhar Nov 3, 2023
1775f25
updates names and adds comments
Nargeshdb Nov 20, 2023
146c5a2
move computations in #6242 to MustCallChecker
Nargeshdb Nov 20, 2023
428692d
merge
Nargeshdb Jan 17, 2024
3949fc4
make MustCallAnalysis consistent with 6242
Nargeshdb Jan 17, 2024
076c498
fix
Nargeshdb Jan 18, 2024
e3a9b56
fix misc error
Nargeshdb Jan 18, 2024
8ed9422
minor fix
Nargeshdb Jan 19, 2024
c2471a7
update
Nargeshdb Jan 19, 2024
c1c5384
Merge branch 'master' into FN-CreatesMustCallFor
Nargeshdb Jan 19, 2024
13a9333
refactor
Nargeshdb Jan 22, 2024
e2883b9
Merge remote-tracking branch 'origin/FN-CreatesMustCallFor' into FN-C…
Nargeshdb Jan 22, 2024
37c45b9
Merge branch 'master' into FN-CreatesMustCallFor
msridhar Feb 10, 2024
1974e25
javadoc
msridhar Feb 10, 2024
4430d05
Merge branch 'master' into FN-CreatesMustCallFor
msridhar Mar 3, 2024
ee8edbd
Merge branch 'master' into FN-CreatesMustCallFor
msridhar Mar 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.checkerframework.checker.mustcall;

import com.google.common.collect.ImmutableSet;
import com.sun.tools.javac.code.Type;
import java.util.Set;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.checker.signature.qual.CanonicalName;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.framework.flow.CFAnalysis;

/**
* The analysis for the Must Call Checker. The analysis is specialized to ignore certain exception
* types; see {@link #isIgnoredExceptionType(TypeMirror)}.
*/
public class MustCallAnalysis extends CFAnalysis {

/**
* Constructs an MustCallAnalysis.
*
* @param checker the checker
* @param factory the type factory
*/
public MustCallAnalysis(BaseTypeChecker checker, MustCallAnnotatedTypeFactory factory) {
super(checker, factory);
}

/**
* The fully-qualified names of the exception types that are ignored by this checker when
* computing dataflow stores.
*/
protected static final Set<@CanonicalName String> ignoredExceptionTypes =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Nargeshdb my question remains, shouldn't this set of exceptions be configurable using the same setting added in #6242?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relatedly, #6242 added a mechanism for matching all subtypes of an exception type in addition to matching by name. We should try to be as consistent as possible here and share code if possible

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Nargeshdb any thoughts on the above?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've already addressed your comment here. There is a method in this class like what we have here, but it's overridden in MustCallAnnotatedTypeFactory to match all subtypes of an exception type. Is there anything else that I missed regarding consistency with 6242?

Copy link
Contributor

@msridhar msridhar Mar 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for being unclear. #6242 added an option -AresourceLeakIgnoredExceptions to specify which exception types should be ignored by the RLC. This PR moves some of that logic to the Must Call Checker. And, as far as I can see, the method in this class, MustCallAnalysis, ignored the setting entirely. This is similar to what is done in the CalledMethodsAnalysis, I agree. What I don't fully understand is:

  1. Why does this PR added the method MustCallAnalysis#isIgnoredExceptionType()? Was this an oversight from before and it should have been there all along? This method seems unrelated to the command line setting.
  2. Why does this PR move the logic for the ignored exceptions command-line setting from the Resource Leak Checker to the Must Call Checker?

Thanks a lot!

ImmutableSet.of(
// Use the Nullness Checker instead.
NullPointerException.class.getCanonicalName(),
// Ignore run-time errors, which cannot be predicted statically. Doing
// so is unsound in the sense that they could still occur - e.g., the
// program could run out of memory - but if they did, the checker's
// results would be useless anyway.
Error.class.getCanonicalName(),
RuntimeException.class.getCanonicalName());

/**
* Ignore exceptional control flow due to ignored exception types.
*
* @param exceptionType exception type
* @return {@code true} if {@code exceptionType} is a member of {@link #ignoredExceptionTypes},
* {@code false} otherwise
*/
@Override
protected boolean isIgnoredExceptionType(TypeMirror exceptionType) {
return ignoredExceptionTypes.contains(
((Type) exceptionType).tsym.getQualifiedName().toString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.checker.mustcall.qual.CreatesMustCallFor;
import org.checkerframework.checker.mustcall.qual.InheritableMustCall;
Expand Down Expand Up @@ -164,6 +166,11 @@ protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
Arrays.asList(MustCall.class, MustCallUnknown.class, PolyMustCall.class));
}

@Override
protected MustCallAnalysis createFlowAnalysis() {
return new MustCallAnalysis(checker, this);
}

@Override
protected TreeAnnotator createTreeAnnotator() {
return new ListTreeAnnotator(super.createTreeAnnotator(), new MustCallTreeAnnotator(this));
Expand Down Expand Up @@ -225,6 +232,15 @@ protected void constructorFromUsePreSubstitution(
super.constructorFromUsePreSubstitution(tree, type);
}

@Override
public boolean isIgnoredExceptionType(TypeMirror exceptionType) {
if (exceptionType.getKind() == TypeKind.DECLARED) {
return MustCallAnalysis.ignoredExceptionTypes.contains(
TypesUtils.getQualifiedName((DeclaredType) exceptionType));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment here about relationship to #6242

}
return false;
}

/**
* Class to implement the customized semantics of {@link MustCallAlias} (and {@link PolyMustCall})
* annotations; see the {@link MustCallAlias} documentation for details.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
Expand All @@ -16,8 +19,11 @@
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.ConditionalTransferResult;
import org.checkerframework.dataflow.analysis.RegularTransferResult;
import org.checkerframework.dataflow.analysis.TransferInput;
import org.checkerframework.dataflow.analysis.TransferResult;
import org.checkerframework.dataflow.cfg.block.ExceptionBlock;
import org.checkerframework.dataflow.cfg.node.AssignmentNode;
import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
Expand All @@ -27,7 +33,6 @@
import org.checkerframework.dataflow.cfg.node.SwitchExpressionNode;
import org.checkerframework.dataflow.cfg.node.TernaryExpressionNode;
import org.checkerframework.dataflow.expression.JavaExpression;
import org.checkerframework.framework.flow.CFAnalysis;
import org.checkerframework.framework.flow.CFStore;
import org.checkerframework.framework.flow.CFTransfer;
import org.checkerframework.framework.flow.CFValue;
Expand Down Expand Up @@ -60,6 +65,9 @@ public class MustCallTransfer extends CFTransfer {
/** True if -AnoCreatesMustCallFor was passed on the command line. */
private final boolean noCreatesMustCallFor;

/** A set of stores for the exceptional paths */
private @Nullable Map<TypeMirror, CFStore> exceptionalStores;
Nargeshdb marked this conversation as resolved.
Show resolved Hide resolved

/**
* True if -AenableWpiForRlc was passed on the command line. See {@link
* ResourceLeakChecker#ENABLE_WPI_FOR_RLC}.
Expand All @@ -71,7 +79,7 @@ public class MustCallTransfer extends CFTransfer {
*
* @param analysis the analysis
*/
public MustCallTransfer(CFAnalysis analysis) {
public MustCallTransfer(MustCallAnalysis analysis) {
super(analysis);
atypeFactory = (MustCallAnnotatedTypeFactory) analysis.getTypeFactory();
noCreatesMustCallFor =
Expand Down Expand Up @@ -163,7 +171,54 @@ public TransferResult<CFValue, CFStore> visitMethodInvocation(
lubWithStoreValue(store, targetExpr, defaultType);
}
}

if (n.getBlock() instanceof ExceptionBlock) {
exceptionalStores = makeExceptionalStores(n, result);
// Update stores for the exceptional paths to handle cases like:
// https://github.com/typetools/checker-framework/issues/6050
if (result.containsTwoStores()) {
result =
new ConditionalTransferResult<>(
result.getResultValue(),
result.getThenStore().copy(),
result.getElseStore().copy(),
exceptionalStores);
} else {
result =
new RegularTransferResult<>(
result.getResultValue(), result.getRegularStore().copy(), exceptionalStores);
}
exceptionalStores = null;
Nargeshdb marked this conversation as resolved.
Show resolved Hide resolved
}
}

return result;
}

/**
* Create a set of stores for the exceptional paths out of the block containing {@code node}. This
* allows propagation, along those paths, of the fact that the method being invoked in {@code
* node} was definitely called.
*
* @param node a method invocation
* @param input the transfer input associated with the method invocation
* @return a map from types to stores. The keys are the same keys used by {@link
* ExceptionBlock#getExceptionalSuccessors()}. The values are copies of the regular store from
* {@code input}.
*/
private Map<TypeMirror, CFStore> makeExceptionalStores(
Nargeshdb marked this conversation as resolved.
Show resolved Hide resolved
MethodInvocationNode node, TransferResult<CFValue, CFStore> input) {
if (!(node.getBlock() instanceof ExceptionBlock)) {
// This can happen in some weird (buggy?) cases:
// see https://github.com/typetools/checker-framework/issues/3585
return Collections.emptyMap();
}

ExceptionBlock block = (ExceptionBlock) node.getBlock();
Map<TypeMirror, CFStore> result = new LinkedHashMap<>();
block
.getExceptionalSuccessors()
.forEach((tm, b) -> result.put(tm, input.getRegularStore().copy()));
return result;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2126,7 +2126,6 @@ private void propagateObligationsToSuccessorBlock(
// an exceptional path.
continue;
}

// Which stores from the called-methods and must-call checkers are used in
// the consistency check varies depending on the context. The rules are:
// 1. if the current block has no nodes (and therefore the store must come from
Expand All @@ -2139,17 +2138,7 @@ private void propagateObligationsToSuccessorBlock(
// set in the successor store, use the current blocks' CM and MC stores,
// which contain whatever information is true before this (empty) block.
// 2. if the current block has one or more nodes, always use the CM store after
// the last node. To decide which MC store to use:
// 2a. if the last node in the block is the invocation of an
// @CreatesMustCallFor method that might throw an exception, and the
// consistency check is for an exceptional path, use the MC store
// immediately before the method invocation, because the method threw an
// exception rather than finishing and therefore did not actually create
// any must-call obligation, so the MC store after might contain
// must-call obligations that do not need to be fulfilled along this
// path.
// 2b. in all other cases, use the MC store from after the last node in
// the block.
// the last node.
CFStore mcStore;
AccumulationStore cmStore;
if (currentBlockNodes.size() == 0 /* currentBlock is special or conditional */) {
Expand All @@ -2174,18 +2163,12 @@ private void propagateObligationsToSuccessorBlock(
cmStore = typeFactory.getStoreAfter(last);
cmStoreAfter.put(last, cmStore);
}
// If this is an exceptional block, check the MC store beforehand to avoid
// issuing an error about a call to a CreatesMustCallFor method that might
// throw an exception. Otherwise, use the store after.
if (exceptionType != null && isInvocationOfCreatesMustCallForMethod(last)) {
mcStore = mcAtf.getStoreBefore(last); // 2a. (MC)

if (mcStoreAfter.containsKey(last)) {
mcStore = mcStoreAfter.get(last);
} else {
if (mcStoreAfter.containsKey(last)) {
mcStore = mcStoreAfter.get(last);
} else {
mcStore = mcAtf.getStoreAfter(last); // 2b. (MC)
mcStoreAfter.put(last, mcStore);
}
mcStore = mcAtf.getStoreAfter(last); // 2b. (MC)
mcStoreAfter.put(last, mcStore);
}
}

Expand Down Expand Up @@ -2222,21 +2205,6 @@ private boolean aliasInScopeInSuccessor(AccumulationStore successorStore, Resour
return successorStore.getValue(alias.reference) != null;
}

/**
* Returns true if node is a MethodInvocationNode of a method with a CreatesMustCallFor
* annotation.
*
* @param node a node
* @return true if node is a MethodInvocationNode of a method with a CreatesMustCallFor annotation
*/
private boolean isInvocationOfCreatesMustCallForMethod(Node node) {
if (!(node instanceof MethodInvocationNode)) {
return false;
}
MethodInvocationNode miNode = (MethodInvocationNode) node;
return typeFactory.hasCreatesMustCallFor(miNode);
}

/**
* Finds {@link Owning} formal parameters for the method corresponding to a CFG.
*
Expand Down
3 changes: 3 additions & 0 deletions checker/tests/resourceleak/ACSocketTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -360,10 +360,13 @@ void createNewServerSocket(InetSocketAddress address, boolean b, boolean c) thro
ServerSocket socket;

if (b) {
// :: error: required.method.not.called
msridhar marked this conversation as resolved.
Show resolved Hide resolved
socket = new ServerSocket();
} else if (c) {
// :: error: required.method.not.called
socket = new ServerSocket();
} else {
// :: error: required.method.not.called
socket = new ServerSocket();
}

Expand Down
79 changes: 79 additions & 0 deletions checker/tests/resourceleak/AssignNullInExceptionBlock.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import org.checkerframework.checker.mustcall.qual.*;
Nargeshdb marked this conversation as resolved.
Show resolved Hide resolved
import org.checkerframework.checker.nullness.qual.*;

class AssignNullInExceptionBlock {

static class Foo {}

static Foo makeFoo() {
return new Foo();
}

static Foo makeFoo1() {
throw new UnsupportedOperationException();
}

static Foo makeFoo2() throws UnsupportedOperationException {
return new Foo();
}

static Foo makeFoo3() throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}

Foo fooField;

void test1() {
try {
fooField = makeFoo();
} catch (Exception e) {
Foo f = null;
fooField = f;
}
}

void test2() {
try {
fooField = makeFoo1();
} catch (Exception e) {
Foo f = null;
fooField = f;
}
}

void test3() {
Foo f = null;
try {
fooField = makeFoo1();
} catch (Exception e) {
fooField = f;
}
}

void test4() {
try {
fooField = makeFoo2();
} catch (Exception e) {
Foo f = null;
fooField = f;
}
}

void test5() {
try {
fooField = makeFoo3();
} catch (Exception e) {
Foo f = null;
fooField = f;
}
}

void test6() {
Foo f = null;
try {
fooField = makeFoo3();
} catch (Exception e) {
fooField = f;
}
}
}