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

Fix unsound handling of catch parameters and throws clauses #436

Open
wants to merge 73 commits into
base: master
Choose a base branch
from

Conversation

Ao-senXiong
Copy link
Member

@Ao-senXiong Ao-senXiong commented Mar 16, 2023

Fixes #363. This PR revealed the need for #494 and depends on it being merged first.

@Ao-senXiong Ao-senXiong force-pushed the add-tests-throw-initialization branch from 0abca68 to 0fd1c05 Compare March 20, 2023 03:53
@Ao-senXiong Ao-senXiong changed the title add tests for EISOPissue363 add tests and fix the unsoundness for EISOPissue363 May 26, 2023
Copy link
Member

@wmdietl wmdietl 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 improvements to this PR! I have another long list of comments.

@@ -17,15 +17,15 @@ class Inner {}
// Type var test
<E extends @UI PolyUIException> void throwTypeVarUI1(E ex1, @UI E ex2) throws PolyUIException {
if (flag) {
// :: error: (throw.type.invalid)
// :: error: (throw.type.incompatible)
Copy link
Member

Choose a reason for hiding this comment

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

Changing the error key also requires a change to this file:
https://github.com/eisop/checker-framework/blob/e604d8d10a38d09f7c3e94c4d614b2d729f48085/framework/src/main/java/org/checkerframework/common/basetype/messages.properties#L35C12-L35C19

incompatible is the better term, as there is a comparison between two types.
In a previous discussion we said we do this renaming in a separate PR. Was there a change of mind?

Copy link
Member Author

Choose a reason for hiding this comment

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

I have opened a new PR here #618.

Copy link
Member

Choose a reason for hiding this comment

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

So you should undo these changes in this PR, so that we can merge this PR independently of #618.

import org.checkerframework.checker.initialization.qual.NotOnlyInitialized;
import org.checkerframework.checker.initialization.qual.UnknownInitialization;

class MyException extends Exception {
Copy link
Member

Choose a reason for hiding this comment

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

Try having just one top-level class in tests or include something, like 363a in all top-level class names.
Otherwise, it is very easy for two different tests to define a generic class name like MyException and strange behavior can result.

this.cause = cause;
}

MyException(@Initialized EISOPIssue363a cause, String dummy) {
Copy link
Member

Choose a reason for hiding this comment

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

This just makes the @Initialized qualifier explicit. Is that the intention?
Should there be a separate constructor that takes an @UnderInitialization parameter, to test all possibilities?

throw new MyException(this, 0);
}

EISOPIssue363a(char dummy) throws MyException {
Copy link
Member

Choose a reason for hiding this comment

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

It's rather misleading that this constructor, with a char parameter, invokes the MyException constructor with a String argument. The following constructor with a String parameter, invokes a different constructor. Maybe these can be aligned a bit better?

public static void test3(String[] args) {
try {
EISOPIssue363a obj = new EISOPIssue363a();
} catch (@Initialized MyException ex) {
Copy link
Member

Choose a reason for hiding this comment

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

This is the same as test1, just with an explicit qualifier. Put them next to each other and add a comment?

getExceptionParameterLowerBoundAnnotationsCached();
VariableTree excParamTree = tree.getParameter();
AnnotatedTypeMirror excParamType = atypeFactory.getAnnotatedType(excParamTree);

// List<AnnotatedTypeMirror> exceptionList =
// atypeFactory.getAnnotatedType(methodTree).getThrownTypes();
Copy link
Member Author

Choose a reason for hiding this comment

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

@wmdietl I try to compare the type of exception that method body throw and the type of catch parameter. Do you have any suggestion on how to get what exception the method body throws?

Copy link
Member Author

Choose a reason for hiding this comment

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

@wmdietl I think we should allow the following code as the exception may not be catched like how Java handle this:

void foo() throw @UnknowInitialization myException {}

void test(){
    try {
        foo()
    } catch (MyException e) {}
}

@wmdietl
Copy link
Member

wmdietl commented Jun 26, 2024

@Ao-senXiong Can you look into wrapping up this change, without the renaming that is done in #618? Let's first fix the soundness issue and then improve the naming.

Copy link
Member

@wmdietl wmdietl 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 getting back to this.

if (methodTree != null) {
List<AnnotatedTypeMirror> allowedExceptions =
atypeFactory.getAnnotatedType(methodTree).getThrownTypes();
if (allowedExceptions != null) {
Copy link
Member

Choose a reason for hiding this comment

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

allowedExceptions should never be null, so this check is redundant.

for (AnnotatedTypeMirror exception : allowedExceptions) {
Types typesUtil = atypeFactory.getProcessingEnv().getTypeUtils();
if (typesUtil.isSubtype(
exception.getUnderlyingType(), throwType.getUnderlyingType())) {
Copy link
Member

Choose a reason for hiding this comment

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

What is the logic for this subtype test? Shouldn't it be the other way around?
Why is it only comparing the TypeMirrors and not considering annotations?

Copy link
Member Author

Choose a reason for hiding this comment

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

The purpose here is to update required variable, which is used to check subtyping relationship between actual throw type and required.

The logic is: we should only update require only if the exception throwed is a subtype of one of the decleared throw exception in method signature. For example for the following code, we should update required to @Initailized instead of use top annotation in the hierarchy. And since actual throw exception has @UnderInitialization type. so throw type invalid error should be issued

    EISOPIssue363(int dummy1, int dummy2, int dummy3) throws @Initailized MyException {
        // :: error: (throw.type.invalid)
        throw new MyException(this, "UnderInitialization");
    }

Copy link
Member Author

Choose a reason for hiding this comment

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

add test case for methodinvocation.

break;
}
}
}
Copy link
Member

Choose a reason for hiding this comment

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

You didn't come back to this comment. Doesn't this mean that from a throw statement, you need to look at both the enclosing catch clauses and only finally at the throws clause on the enclosing method?

@wmdietl wmdietl assigned Ao-senXiong and unassigned wmdietl Sep 4, 2024
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.

Nullness Checker unsoundly assumes catch-clause parameters to be initialized
2 participants