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

Update region computation. #91

Merged
merged 34 commits into from
Nov 23, 2022
Merged

Update region computation. #91

merged 34 commits into from
Nov 23, 2022

Conversation

nimakarimipour
Copy link
Member

@nimakarimipour nimakarimipour commented Nov 11, 2022

CI

Please note that CI will fail for this PR as it requires release of NullAway:0.10.5.
NullAway:0.10.5 is released and CI should pass.


This PR updates the region computation in Type-Annotator-Scanner according to latest changes in NullAway following PR658.

Also in this PR the infrastructure for keeping compatibility with older versions of NullAway (0.10.4 or less) is implemented.

@nimakarimipour nimakarimipour added the enhancement New feature or request label Nov 11, 2022
@nimakarimipour nimakarimipour self-assigned this Nov 11, 2022
Copy link
Member

@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.

What is here LGTM, though I know we plan to update this one to keep compatibility with NullAway 0.10.4. I guess we can approve after this PR is finalized.

Copy link
Member

@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.

Mostly LGTM, just a few comments

with:
java-version: 11
distribution: 'adopt'
Copy link
Member

Choose a reason for hiding this comment

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

Nit: 'adopt' is out of date, use 'temurin'

Copy link
Member Author

@nimakarimipour nimakarimipour Nov 18, 2022

Choose a reason for hiding this comment

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

distribution: 'adopt'
- name: Build with Gradle
run: ./gradlew :core:test --scan --no-daemon -Pnullaway-test-version=0
Copy link
Member

Choose a reason for hiding this comment

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

Probably don't need --no-daemon here?

Copy link
Member Author

@nimakarimipour nimakarimipour Nov 18, 2022

Choose a reason for hiding this comment

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

Yes, that doesn't seem necessary. 08d9e2c

* Responsible for performing tasks related to NullAway / Type Annotator Scanner serialization
* features.
*/
public interface Adapter {
Copy link
Member

Choose a reason for hiding this comment

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

Let's be more verbose here and call this NullAwayVersionAdapter

Copy link
Member Author

@nimakarimipour nimakarimipour Nov 18, 2022

Choose a reason for hiding this comment

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

import edu.ucr.cs.riple.core.Config;

/** Base class for all adapters. */
public abstract class AdapterAbstractClass implements Adapter {
Copy link
Member

Choose a reason for hiding this comment

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

I don't think it's worth creating this abstract class just to hold a single protected field. I would get rid of it.

Copy link
Member Author

@nimakarimipour nimakarimipour Nov 18, 2022

Choose a reason for hiding this comment

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

Deleted in: b51475a

* <li>Type Annotator Scanner: 1.3.3 or below
* </ul>
*/
public class NullAwayAdapterVersion0 extends AdapterAbstractClass {
Copy link
Member

Choose a reason for hiding this comment

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

If we go with NullAwayVersionAdapter for the interface, this can be NullAwayVersion0Adapter, or simply NullAwayV0Adapter

Copy link
Member Author

@nimakarimipour nimakarimipour Nov 18, 2022

Choose a reason for hiding this comment

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

Sure, sound good. b51475a

Thank you for the suggestions in renaming.

@nimakarimipour
Copy link
Member Author

@msridhar Thank you for the review. I resolved all comments.

Copy link
Collaborator

@lazaroclapp lazaroclapp left a comment

Choose a reason for hiding this comment

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

Would like to see CI passing on the recently released NullAway 0.10.5 before landing this, but here are my comments so far :)

path: build/reports/tests/test
Copy link
Collaborator

Choose a reason for hiding this comment

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

I assume either now or in a follow up diff, we want to change all the above distribution: 'adopt' to temurin. Not really needed for this PR (and it's already quite large) but worth keeping in 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.

#98

distribution: 'temurin'
- name: Build with Gradle
run: ./gradlew :core:test --scan -Pnullaway-test-version=0
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we call this something more accurate like nullaway-serialization-format-version=0. It is not directly the NullAway version itself.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure makes sense. 69e537b

@@ -29,14 +29,14 @@
public class TrackerNodeDisplay implements Display {

public final String callerClass;
public final String callerMethod;
public final String callerMember;
public final String calleeClass;
public final String member;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't this be renamed to calleeMember or similar? Or maybe even calleeMethod? Is it ever anything other than a method? Can it be a field? (If so, then the term "callee" is not exactly correct either...)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Wondering if destinationClass, destinationMember, sourceClass, sourceMember would be better names here... hard to tell without having the description of the algorithm, so maybe we can keep these names for now, but they are a bit misleading (cc: @msridhar for potential naming suggestions)

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, they it should be calleeMember and yes it can be a field as well. I understand that calleeMember is not accurate as it can be a field as well. I am going to change it for now to calleeMember but it should be changed in future. 851f8eb

Copy link
Member

Choose a reason for hiding this comment

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

I think "caller" here refers to the region containing a use of some member, and "callee" refers to the used member. So maybe regionClass, regionMember, usedClass, usedMember? @nimakarimipour if you agree we can just make the change. Otherwise, we can leave a TODO comment and fix in a follow-up.

Copy link
Member Author

Choose a reason for hiding this comment

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

@msridhar Sure, we are already renaming a member of this class in this PR, I will update it now. Thank you for the suggestions.

Copy link
Member Author

@nimakarimipour nimakarimipour Nov 19, 2022

Choose a reason for hiding this comment

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

"}")
.setExpectedOutputs(
new TrackerNodeDisplay("edu.ucr.A", "f0", "edu.ucr.B", "foo"),
new TrackerNodeDisplay("edu.ucr.A", "f0", "edu.ucr.B", "foo"),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why are some entries, like the one just above, repeated?

Copy link
Member Author

Choose a reason for hiding this comment

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

Methods matchIdentifier and matchMemberSelect might produce the same output, I just thought it might not worth it to keep all the outputs in a set and check for every output if it is already serialized as they will be deserialized and converted into a set in core. Also I didn't want to change the test infra in this PR. Please let me know if you think I should change the infra or make a list and prevent duplicates serialize in output.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Makes sense to me, just wanted to understand this. I do think eventually you might want tests to not need this repetition, but it's low priority and not needed for this PR.

// Can be enclosed by a field declaration tree.
VariableTree fieldDeclTree = ASTHelpers.findEnclosingNode(path, VariableTree.class);
if (fieldDeclTree != null) {
fieldSymbol = ASTHelpers.getSymbol(fieldDeclTree);
Copy link
Collaborator

Choose a reason for hiding this comment

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

So, in this path, we aren't actually checking that ASTHelpers.getSymbol(fieldDeclTree) is a field, right?

What happens on a test case like:

public class A {
   private static Object f;
   static {
     Object a = B.foo();
     Object b = B.bar();
     if (a.hashCode() > b.hashCode()) {
       f = a;
     } else {
       f = b;
     }
   }
}
public class B {
   public static Object foo() {...}
   public static Object bar() {...}
}

Wouldn't that generate spurious A.a and A.b regions, even though a and b are locals and not members of A?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, we are creating A.b and A.a. In previous approach the enclosing class of these fields (a and b) was still considered to be A and we would have generated region A.null. In the new approach we are making A.a and A.b. I am not sure if they are actually wrong as long as NullAway considers the identical region for triggered error and fix. Please let me know if we should still change this.

Copy link
Member

Choose a reason for hiding this comment

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

@nimakarimipour the issue here is that there are no fields A.a and A.b; a and b are local variables inside the static initializer. Ideally, we would not create regions for entities that are not fields. I would think you could easily add a check that the symbol is actually for a field? You can check symbol.getKind().equals(ElementKind.FIELD)

Copy link
Member Author

Choose a reason for hiding this comment

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

@msridhar @lazaroclapp Sure, it is wrong to create a region for A.a and A.b conceptually. To address this issue the suggested change will work and it will create correct results. But if we apply this change, it will require a follow up change in NullAway as well. If we do not apply this change, we will not get inaccurate results as NullAway and Type-Annotator-Scanner uses the same logic to label regions. And if we apply this change here in this PR, we might get inaccurate results using NullAway:0.10.5. Please let me know if I should apply the change anyway and make a follow up on NullAway as well. Thank you.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we should at least track this issue somewhere. I would be fine with it not being fixed in this version of the auto-annotator, so we can keep compatibility with NullAway 0.10.5 (though this is again kind of an issue where we are breaking fix serialization format compatibility... wonder how common these breaking changes will be in the future). The main problem is that I am not sure what the auto-annotator will try to do with the code above then: will it try to add @Nullable to those local variable declarations? Will it just consider it an error where it can't make a fix on the file? I am also wondering if this can cause broken code to be generated by the auto-annotator if e.g. the @Nullable annotation is not applicable to local variable declarations...

Copy link
Member Author

Choose a reason for hiding this comment

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

@msridhar @lazaroclapp It is still capable of examining @Nullable annotations on a and b, however NullAway will not suggest a fix for these variables since they are local variables.

The only case that will not be able to handle is seeing a dereference error in their initialization expression.

Class Bar {
   static {
        @Nullable Foo a;
        Foo b = a.deref();
    }
}

With current labeling, the region for dereferencing a.bar() is Bar#b and we cannot add a SuppressWarnings on that.

But in the suggested version which is labeling this region as Bar#null it is not still possible to silence this error with @SuppressWarnings as well. (For both cases the only possible solution is to add the SuppressWarnings at the class level).

In summary, the current implementation does not produce inaccurate results and it will not leave an error which the proposed approach can resolve.

Copy link
Collaborator

@lazaroclapp lazaroclapp Nov 22, 2022

Choose a reason for hiding this comment

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

(For both cases the only possible solution is to add the SuppressWarnings at the class level).

So, just to be clear: What is the tool's current action on the code below:

public class A {
   private static Object f;
   static {
     Object a = B.foo();
     Object b = B.bar();
     if (Objects.hashCode(a) > Objects.hashCode(b)) {
       f = a;
     } else {
       f = b;
     }
   }
}
public class B {
   @Nullable
   public static Object foo() {...}
   @Nullable
   public static Object bar() {...}
}

Is it to add a suppression on the class level on A or is it to leave the error or is it something else?

Changed to Objects.hashCode(...) so there isn't a third-party call taking null in there...

Copy link
Member Author

Choose a reason for hiding this comment

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

@lazaroclapp I ran the example you provided, please find the output below:

public class A {
    @Nullable
    private static Object f;
    static {
        Object a = B.foo();
        Object b = B.bar();
        if (Objects.hashCode(a) > Objects.hashCode(b)) {
            f = a;
        } else {
            f = b;
        }
    }
}
class B {
    @Nullable
    public static Object foo() {return null;}
    @Nullable
    public static Object bar() {return null;}
}

Leaving no remaining NullAway error.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added a unit test 2218ea3

Copy link
Member

Choose a reason for hiding this comment

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

In summary, the current implementation does not produce inaccurate results and it will not leave an error which the proposed approach can resolve.

Ok, in this case, I am fine with going with this change, though we should track fixing this issue of invalid field regions everywhere (NullAway, scanner, annotator) as a follow-up.

*
* @param location Location of the targeted element.
* @param values Values in row of a TSV file.
* @return Corresponding Error instance with the passed values.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Error -> Fix

Copy link
Member Author

Choose a reason for hiding this comment

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

* corresponding {@link TrackerNode} instance.
*
* @param values Values in row of a TSV file.
* @return Corresponding Error instance with the passed values.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Error -> TrackerNode

Copy link
Member Author

Choose a reason for hiding this comment

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

Thank you for noticing this. 204ec6b

}

@Override
public Fix deserializeFix(Location location, String[] values) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Wouldn't it be a good idea to have an initial precondition here (and in all similar methods taking String[] values) checking for the number of elements in values? Presumably different serialization formats will have different number of fields?

Copy link
Member Author

@nimakarimipour nimakarimipour Nov 19, 2022

Choose a reason for hiding this comment

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

Sure, sounds good. 94d4efe

public FieldInitializationAnalysis(Path path) {
super(path);
public FieldInitializationAnalysis(Config config) {
super(config, config.target.dir.resolve("field_init.tsv"));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could we extract "field_init.tsv" into a global constant, like we do below for MethodDeclarationTree:

    super(config, config.target.dir.resolve(Serializer.METHOD_INFO_FILE_NAME));

Copy link
Member Author

@nimakarimipour nimakarimipour Nov 19, 2022

Choose a reason for hiding this comment

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

Actually "field_init.tsv" is not an output of TypeAnnotatorScanner and it might not be preferred to declare the file name in its Serializer. But we should definitely declare the file as a global constant. I extracted it into the user class. 2db1d9a

@nimakarimipour
Copy link
Member Author

@lazaroclapp Thank you very much for the review, this is ready for another round.

Copy link
Collaborator

@lazaroclapp lazaroclapp left a comment

Choose a reason for hiding this comment

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

All changes since my last review look good, and most comments addressed, however, this one still stands, see @msridhar's note.

Copy link
Collaborator

@lazaroclapp lazaroclapp left a comment

Choose a reason for hiding this comment

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

Ok, given the discussion above and the results in the unit test, I think the chance of a case that will break due to the spurious local regions is sufficiently small. This LGTM! Thanks, Nima, and apologies for all the back and forth! 🙂

.toDepth(5)
.addExpectedReports(new TReport(new OnField("A.java", "test.A", singleton("f")), -3))
.enableForceResolve()
.start();
Copy link
Collaborator

Choose a reason for hiding this comment

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

👍

@nimakarimipour
Copy link
Member Author

@msridhar @lazaroclapp Thank you very much for the review. I will land it now.

@nimakarimipour nimakarimipour merged commit e4b482e into master Nov 23, 2022
@nimakarimipour nimakarimipour deleted the nimak/nullaway_update branch November 23, 2022 22:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants