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

Add @EnsuresCalledMethodsOnException #6271

Conversation

Calvin-L
Copy link
Contributor

Fixes #6204 and adds a useful feature to the Called Methods Checker.

See also this comment.

Motivation

It is very common for resource-handling code to close owned resources even on exceptional return. The existing annotations like @EnsuresCalledMethods and @Owning only describe what happens on normal return, and there is no way to write a stronger contract that says a method always closes a resource. (As far as I know, this limitation is common to all post-condition annotations in the Checker Framework.)

This PR adds @EnsuresCalledMethodsOnException, a new post-condition annotation that says what the method guarantees when it throws (subclasses of) java.lang.Exception.

Why a new annotation instead of strengthening @EnsuresCalledMethods?

I added an example in the manual for when you might want @EnsuresCalledMethodsOnException without promising @EnsuresCalledMethods. In short, the new annotation interacts well with @Owning, enabling users to write a contract that says "this method either takes ownership of the resource or closes it immediately and throws an exception".

Why only for Exception and not all Throwables?

I believe it is essentially impossible to guarantee no resource leaks in the presence of runtime errors: if socket.close() throws StackOverflowError, what can you possibly do?

We should discuss whether limiting @EnsuresCalledMethodsOnException to only Exception is right or not. There might be a good reason to generalize to all Throwables, or to have some complex set of exceptions like "all subclasses of Throwable except these specific subclases of Error".

Modified handling for destructors

The Resource Leak Checker requires a stronger contract for "destructor-like" methods. They must close their resources even when they throw an exception. Previously, that was accomplished with a special-case check. Now it can be accomplished in terms of the new annotation.

To avoid a breaking change, I have made @EnsuresCalledMethods imply an identical @EnsuresCalledMethodsOnException when it appears on a destructor. In my opinion this is just as hacky as the previous approach, but it ensures backwards compatibility while unifying some related code. This is the change that fixes #6204, since callers of destructors now benefit from the implied exceptional post-condition.

Misc notes

We should discuss if the name @EnsuresCalledMethodsOnException is good or not. I don't know if there is a precedent for this kind of annotation.

In the future, we might want to add framework support for this kind of post-condition annotation, like how @EnsuresCalledMethods is mostly handled by existing machinery.

Originally I had hoped to have this done by the November release, but it took a while to put together. We should take the time to review it carefully, and I'll look forward to using it in December. :)

Instead of using a "hidden" contract for `EnsuresCalledMethods` for
destructors, now destructors must explicitly list
`EnsuresCalledMethodsOnException` as well.  This is conceptually
simpler.

For backwards compatibility, an `EnsuresCalledMethods` annotation on a
destructor implies an identical `EnsuresCalledMethodsOnException`.
Copy link
Contributor

@kelloggm kelloggm left a comment

Choose a reason for hiding this comment

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

Overall this looks like a great change! There are definitely some things I'm concerned about, but most of those are #6276, which this PR didn't introduce (just brought to my attention).

I'd like to have a syntactic sugar for the common case on destructors where both @EnsuresCalledMethods and @EnsuresCalledMethodsOnException are desirable. I'm not sure that needs to block landing this PR, though - we can add it in a follow-up change.

Other than that, my comments are mostly minor. @msridhar should probably take a look next.


/**
* Indicates that the method, if it terminates by throwing an exception, always invokes the given
* methods on the given expressions. This annotation is repeatable, which means that users can write
Copy link
Contributor

Choose a reason for hiding this comment

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

If a user wants to write a method that guarantees that some methods are called on both the normal and exceptional exit paths, they'd need to write both @EnsuresCalledMethods and @EnsuresCalledMethodsOnException annotations. I don't think this is a good user experience: it would be better to have a single annotation for "ensures some methods are called on all exit paths" that logically means both normal and exceptional exits. I'd call such an annotation @EnsuresCalledMethodsOnAllPaths or something similar, I think.

This PR doesn't need to implement it, but it's probably something that we should consider. This is also the semantics of destructors exactly, so we know that there is a use case for it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed that we need such an annotation and that we can do it in a follow-up to this PR. I would strongly prefer to have at least an alias for the annotation that has fewer characters to type than @EnsuresCalledMethodsOnAllPaths; maybe @AlwaysCalls? But we can discuss that as part of the follow-up.

kelloggm
kelloggm previously approved these changes Oct 31, 2023
Copy link
Contributor

@kelloggm kelloggm left a comment

Choose a reason for hiding this comment

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

LGTM, other than #6276. This PR doesn't make that worse, so I think it can be landed as-is. @msridhar needs to review this first, though.

@kelloggm kelloggm assigned msridhar and unassigned kelloggm Oct 31, 2023
Copy link
Contributor

@msridhar msridhar left a comment

Choose a reason for hiding this comment

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

Overall, looks great! Just a few minor comments


/**
* Indicates that the method, if it terminates by throwing an exception, always invokes the given
* methods on the given expressions. This annotation is repeatable, which means that users can write
Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed that we need such an annotation and that we can do it in a follow-up to this PR. I would strongly prefer to have at least an alias for the annotation that has fewer characters to type than @EnsuresCalledMethodsOnAllPaths; maybe @AlwaysCalls? But we can discuss that as part of the follow-up.

Copy link
Contributor

@msridhar msridhar left a comment

Choose a reason for hiding this comment

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

Thanks for all the fixes, looks great!

@msridhar msridhar merged commit 4a4a500 into typetools:master Nov 6, 2023
29 checks passed
@Calvin-L Calvin-L deleted the ensures-called-methods-on-exception-annotation branch November 6, 2023 07:02
wmdietl pushed a commit to eisop/checker-framework that referenced this pull request Dec 5, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Resource Leak Checker: call to super.close() does not satisfy @EnsuresCalledMethods
3 participants