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 Delegation Checker #6609

Draft
wants to merge 174 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
174 commits
Select commit Hold shift + click to select a range
dddb8a3
Add start of manual for Non-Empty Checker
jyoo980 Jan 9, 2024
f5b1188
Add section on Non-Empty Checker checks
jyoo980 Jan 9, 2024
d7e07e7
Adding type hierarchy image
jyoo980 Jan 9, 2024
ce3cde3
Add qualifiers and barebones Non-Empty Checker
jyoo980 Jan 10, 2024
61ce733
Add documentation for quals
jyoo980 Jan 10, 2024
e20d5af
Add postcondition annotations for the Non-Empty Checker
jyoo980 Jan 10, 2024
1a13283
Merge branch 'master' into yoo/nonempty-checker
jyoo980 Jan 10, 2024
93bc49e
Merge branch 'typetools:master' into yoo/nonempty-checker
jyoo980 Jan 10, 2024
433a993
Add postcondition annotations and tests
jyoo980 Jan 10, 2024
5fe370d
Merge branch 'yoo/nonempty-checker' of github.com:jyoo980/checker-fra…
jyoo980 Jan 10, 2024
53ef7e7
Merge branch 'typetools:master' into yoo/nonempty-checker
jyoo980 Jan 10, 2024
134aef7
Fix Javadoc build errors
jyoo980 Jan 10, 2024
cdf043d
Add formatting updates
jyoo980 Jan 11, 2024
eda22bb
Adding descriptions for `@EnsuresNonEmpty` and `@EnsuresNonEmptyIf`
jyoo980 Jan 11, 2024
8824de7
Adding tests for `@EnsuresNonEmpty` and `@EnsuresNonEmptyIf`
jyoo980 Jan 11, 2024
5aeea64
Tweaks to manual
mernst Jan 11, 2024
b9699e1
Merge branch 'yoo/nonempty-checker' of github.com:jyoo980/checker-fra…
mernst Jan 11, 2024
500c249
Update formatting and code example
jyoo980 Jan 12, 2024
9e58eaf
Merge branch 'typetools:master' into yoo/nonempty-checker
jyoo980 Jan 12, 2024
c170355
Add `NonEmptyBottom` and `PolyNonEmpty` to Non-Empty type system
jyoo980 Jan 13, 2024
1e66676
Re-organize tests
jyoo980 Jan 17, 2024
fc61d3d
Adding additional tests for `Set`
jyoo980 Jan 17, 2024
e91c373
Add temporary `@skip-test`
jyoo980 Jan 17, 2024
6fdfc3b
Merge branch 'typetools:master' into yoo/nonempty-checker
jyoo980 Jan 17, 2024
e7fd519
Merge branch 'master' into yoo/nonempty-checker
jyoo980 Jan 19, 2024
00714bc
Merge ../checker-framework-fork-jyoo980-branch-yoo-nonempty-checker i…
mernst Jan 22, 2024
4467854
Set `this.sideEffectsUnrefineAliases` in NonEmptyAnnotatedTypeFactory
mernst Jan 22, 2024
20055de
Merge ../checker-framework-branch-master into yoo/nonempty-checker
mernst Jan 22, 2024
98f218c
Merge branch 'yoo/nonempty-checker' of github.com:jyoo980/checker-fra…
mernst Jan 22, 2024
2c5e9ff
Merge ../checker-framework-fork-jyoo980-branch-yoo-nonempty-checker i…
mernst Jan 22, 2024
99b9242
Merge pull request #1 from mernst/issue6407
jyoo980 Jan 22, 2024
f80abb2
Merge branch 'master' into yoo/nonempty-checker
jyoo980 Jan 22, 2024
db6dab9
Add test for `Set.remove(E)`
jyoo980 Jan 22, 2024
89b4bee
Add tests for immutable map operations
jyoo980 Jan 23, 2024
a9964bc
Merge branch 'typetools:master' into yoo/nonempty-checker
jyoo980 Jan 23, 2024
19a2042
Enable JDK tests
jyoo980 Jan 23, 2024
90de18e
Merge branch 'yoo/nonempty-checker' of github.com:jyoo980/checker-fra…
jyoo980 Jan 23, 2024
a0da89d
Apply Spotless changes
jyoo980 Jan 24, 2024
ee28483
Merge branch 'typetools:master' into yoo/nonempty-checker
jyoo980 Jan 25, 2024
bca7308
Merge branch 'master' into yoo/nonempty-checker
jyoo980 Jan 27, 2024
a53b9ba
Adding tests for iterator operations
jyoo980 Jan 27, 2024
d788fe4
Merge branch 'master' into yoo/nonempty-checker
jyoo980 Jan 29, 2024
28859fb
Start implementing transfer fn. for Non-Empty Checker
jyoo980 Jan 29, 2024
266af5f
Implement `NonEmptyTransfer.visitNotEqual`
jyoo980 Jan 29, 2024
16d8aa1
Pass proper arg
jyoo980 Jan 29, 2024
aec4317
Complete initial impl. of transfer function for Non-Empty Checker
jyoo980 Jan 30, 2024
3fefdb4
Adding failing tests for LTE
jyoo980 Jan 30, 2024
e44ecb8
Comparison refinement working, need to add documentation
jyoo980 Jan 30, 2024
de84e5e
Merge branch 'master' into yoo/nonempty-checker
jyoo980 Jan 30, 2024
fefadec
Document `NonEmptyTransfer` class
jyoo980 Jan 30, 2024
feb14e2
Invert conditionals and comment-out tests
jyoo980 Jan 31, 2024
10ddf62
Update Map.java annotation tests
jyoo980 Jan 31, 2024
92dee7b
Add support for `Map.size` in `NonEmptyTransfer`
jyoo980 Feb 1, 2024
a9b7069
Add switch test case
jyoo980 Feb 1, 2024
cd8d5e1
Add transfer function for switch statments
jyoo980 Feb 2, 2024
f079ad5
Merge branch 'master' into yoo/nonempty-checker
jyoo980 Feb 2, 2024
5d5cf3a
Support refinement for equality comparisons
jyoo980 Feb 2, 2024
a107fb2
Implement implicit Non-Empty refinement for Equals
jyoo980 Feb 5, 2024
2c4dc9b
Merge branch 'master' into yoo/nonempty-checker
jyoo980 Feb 5, 2024
6fc9073
Implement implicit refinement for `NotEqualTo` expressions
jyoo980 Feb 5, 2024
0718311
Merge branch 'master' into yoo/nonempty-checker
jyoo980 Feb 6, 2024
f45e835
Add todo for GLB in strengthening annos
jyoo980 Feb 6, 2024
1c37e41
Test that `Predicate.test` is pure
mernst Feb 6, 2024
2a68280
Merge branch 'yoo/nonempty-checker' of github.com:jyoo980/checker-fra…
mernst Feb 6, 2024
b567d3c
Merge branch 'master' into yoo/nonempty-checker
jyoo980 Feb 8, 2024
ed46d30
Instantiate `NonEmptyTransfer` via reflection
jyoo980 Feb 12, 2024
d72f6bd
Implement refinement for `java.util.List indexOf(Object)`
jyoo980 Feb 13, 2024
0660188
Implement `indexOf(Object)` refinement for `switch` statements
jyoo980 Feb 13, 2024
692b0c4
Add test case
mernst Feb 13, 2024
ac242c2
Add test case for `size()` call in `isEmpty()`
jyoo980 Feb 16, 2024
0c87474
Update code example in manual
jyoo980 Feb 16, 2024
3eceeda
Add manual section and test case for delegation
jyoo980 Feb 16, 2024
649f386
Add type annos for Delegation checking
jyoo980 Feb 17, 2024
f413c61
Merge branch 'master' into yoo/nonempty-checker
jyoo980 Feb 18, 2024
d67005d
Simplify `@Delegate` logic
jyoo980 Feb 20, 2024
8cb8fd3
Add basic `DelegationChecker` implementation
jyoo980 Feb 22, 2024
2174fb7
Remove override for `setRoot` as it appears to crash with NPE
jyoo980 Feb 22, 2024
5d472ae
Merge branch 'master' into yoo/nonempty-checker
jyoo980 Feb 22, 2024
349f825
Make `NonEmptyChecker` extend `DelegationChecker`
jyoo980 Feb 22, 2024
82ec19e
Add test case for invalid delegation
jyoo980 Feb 22, 2024
ff710d5
Implement very basic delegation verification
jyoo980 Feb 23, 2024
3350681
Simplify logic for finding `Override` annotations
jyoo980 Feb 23, 2024
6dbab9a
Update tests
jyoo980 Feb 23, 2024
3c783c6
Document code
jyoo980 Feb 25, 2024
583bdab
Merge remote-tracking branch 'upstream/master' into yoo/nonempty-checker
jyoo980 Feb 25, 2024
f8b5c9f
Fix boolean operator
jyoo980 Feb 25, 2024
471b30d
Support delgation to void methods
jyoo980 Feb 25, 2024
451b831
Merge branch 'master' into yoo/nonempty-checker
jyoo980 Feb 26, 2024
a3c967f
Allow exceptional exits for delegate methods
jyoo980 Feb 27, 2024
d3a3900
Merge remote-tracking branch 'upstream/master' into yoo/nonempty-checker
jyoo980 Feb 27, 2024
dfb58b9
Start obtaining methods from supertype
jyoo980 Feb 27, 2024
44d0b51
Add UnmodifiableTest.java
jyoo980 Feb 29, 2024
1c7d8d4
Expand diagnostics, expand test
mernst Mar 1, 2024
e96fcf5
Adding test case for `Collections.java`
jyoo980 Mar 1, 2024
896b727
Fix merge
jyoo980 Mar 1, 2024
353b096
Merge branch 'master' into yoo/nonempty-checker
jyoo980 Mar 1, 2024
e6fe54a
Comment-out erroneous override checking
jyoo980 Mar 1, 2024
c65f2fc
Implement naive override checking
jyoo980 Mar 3, 2024
8cdcf6d
Add `@DelegatorMustOverride` annotation
jyoo980 Mar 5, 2024
91f127a
Merge branch 'master' into yoo/nonempty-checker
jyoo980 Mar 5, 2024
b00303c
Add Non-Empty Checker tree annotator for `NewArrayTree`
jyoo980 Mar 6, 2024
d0c3ae6
Add test case for `Map.ofEntries`
jyoo980 Mar 6, 2024
01fbe18
Merge branch 'master' into yoo/nonempty-checker
jyoo980 Mar 6, 2024
7156364
Add test cases for `Stream.java`
jyoo980 Mar 7, 2024
9a13edc
Merge branch 'master' into yoo/nonempty-checker
jyoo980 Mar 7, 2024
074f638
Comments
mernst Mar 8, 2024
3a782c0
Merge branch 'master' into yoo/nonempty-checker
jyoo980 Mar 10, 2024
44c72dd
Merge branch 'master' into yoo/nonempty-checker
jyoo980 Mar 11, 2024
e385d0e
Merge remote-tracking branch 'upstream/master' into yoo/nonempty-checker
jyoo980 Mar 11, 2024
2b3cd4b
Add WIP for resolving false positives with non-empty streams
jyoo980 Mar 11, 2024
1ba6043
Refine the result, not the receiver
mernst Mar 12, 2024
c3478c2
Fix typo in documentation
jyoo980 Mar 12, 2024
25ad3c4
Merge branch 'master' into yoo/nonempty-checker
jyoo980 Mar 12, 2024
c044594
Merge remote-tracking branch 'upstream/master' into yoo/nonempty-checker
jyoo980 Mar 12, 2024
7b70a61
Add support for `Stream.reduce(BinaryOperator)`
jyoo980 Mar 12, 2024
3a5c8db
Merge branch 'master' into yoo/nonempty-checker
jyoo980 Mar 13, 2024
6d8dcc5
Add support for `Stream.findAny` and `Stream.findFirst`
jyoo980 Mar 13, 2024
2618787
Merge remote-tracking branch 'upstream/master' into yoo/nonempty-checker
jyoo980 Mar 14, 2024
d08e2ec
Handle case where receiver may be null
jyoo980 Mar 14, 2024
22cd54d
Merge branch 'master' into yoo/nonempty-checker
jyoo980 Mar 14, 2024
9e1028d
Merge branch 'master' into yoo/nonempty-checker
jyoo980 Mar 14, 2024
4a18b42
Merge remote-tracking branch 'upstream/master' into yoo/nonempty-checker
jyoo980 Mar 22, 2024
3fda942
Merge remote-tracking branch 'upstream/master' into yoo/nonempty-checker
jyoo980 Mar 29, 2024
a945675
Apply Spotless changes
jyoo980 Mar 29, 2024
b7d7e17
Merge w. master
jyoo980 Apr 2, 2024
613986d
Merge branch 'master' into yoo/nonempty-checker
jyoo980 Apr 2, 2024
763df84
Merge branch 'master' into yoo/nonempty-checker
jyoo980 Apr 3, 2024
eafc977
Merge branch 'master' into yoo/nonempty-checker
jyoo980 Apr 4, 2024
7103274
Merge branch 'master' into yoo/nonempty-checker
jyoo980 Apr 5, 2024
bb26689
Merge branch 'master' into yoo/nonempty-checker
jyoo980 Apr 8, 2024
4264e4d
Merge branch 'master' into yoo/nonempty-checker
jyoo980 Apr 8, 2024
ef1eec2
Refine types in OptionalTransfer without NonEmptyAnnotatedTypeFactory
jyoo980 Apr 9, 2024
6a89910
Add debugging information
jyoo980 Apr 10, 2024
4d09502
Merge branch 'typetools:master' into yoo/nonempty-optional-transfer-n…
jyoo980 Apr 10, 2024
5b2056e
Apply lint
jyoo980 Apr 10, 2024
7c3527d
Merge branch 'master' into yoo/nonempty-optional-transfer-nonempty-in…
jyoo980 May 1, 2024
50bfa36
Reading @NonEmpty anno in Optional Transfer
jyoo980 May 1, 2024
b5adf0d
Add documentation and debug logs
jyoo980 May 2, 2024
a0abd2f
Add additional logging
jyoo980 May 2, 2024
05b15d0
Remove unnecessary helper methods
jyoo980 May 2, 2024
b1ed401
Merge branch 'master' into yoo/nonempty-optional-transfer-nonempty-in…
jyoo980 May 8, 2024
f8ba782
Merge branch 'typetools:master' into yoo/nonempty-optional-transfer-n…
jyoo980 May 9, 2024
f5b1614
Merge branch 'master' into yoo/nonempty-optional-transfer-nonempty-in…
jyoo980 May 9, 2024
21e21f2
Refactoring check for non-empty method invocation receiver
jyoo980 May 9, 2024
38c5402
Get initial receiver of method invocation
jyoo980 May 13, 2024
fb2bb56
Enable insertion of non-deterministic expressions into the store
jyoo980 May 13, 2024
7d695da
Clean up logging
jyoo980 May 13, 2024
75fd8fa
Start implementing method-level support for `skipDefs` flag
jyoo980 May 13, 2024
7fa7d4a
Merge branch 'master' into yoo/skipdefs-for-methods
jyoo980 May 13, 2024
a8248cc
Correct logic for `JavaExpression.getInitialReceiverOfMethodInvocation`
jyoo980 May 14, 2024
9887291
Add debug logs
jyoo980 May 14, 2024
8e91f0d
Enable `NonEmptyChecker` to access methods to check from the `Optiona…
jyoo980 May 14, 2024
e7906b0
Stub implementation of `SourceChecker.shouldSkipDefs` and make it non…
jyoo980 May 15, 2024
14c7977
Remove unnecessary flags
jyoo980 May 15, 2024
0478e43
Merge branch 'yoo/skipdefs-for-methods' into yoo/nonempty-optional-tr…
jyoo980 May 15, 2024
6d65157
Simplify logic for accessing + checking methods to check in the Non-E…
jyoo980 May 15, 2024
89d3861
Update algorithm to obtain methods to check with Non-Empty Checker
jyoo980 May 15, 2024
fa1539c
Merge branch 'master' into yoo/nonempty-optional-transfer-nonempty-in…
jyoo980 May 16, 2024
0f53325
Merge branch 'master' into yoo/nonempty-optional-transfer-nonempty-in…
jyoo980 May 16, 2024
9a67aab
Override `processMethod`
jyoo980 May 16, 2024
46a2725
Account for case when `TreeUtils.getReceiverTree` returns the receive…
jyoo980 May 17, 2024
572584d
Handle receivers of stream operations that are fields, not locals or …
jyoo980 May 18, 2024
c0c04a0
Merge ../checker-framework-branch-master into yoo/nonempty-optional-t…
mernst May 19, 2024
64c469b
Use `%n`, not `\n`, for platform independence
mernst May 19, 2024
615205d
Minor changes
mernst May 19, 2024
6654033
Remove changes related to the Optional Checker
jyoo980 May 19, 2024
878ffeb
Remove Non-Empty related code
jyoo980 May 19, 2024
6ec0516
Start of Standalone Delegation Checker
jyoo980 May 19, 2024
5c65841
Remove unrelated changes
jyoo980 May 19, 2024
afe12cc
Remove additional unrelated code
jyoo980 May 19, 2024
87f8e0e
Remove manual entry
jyoo980 May 19, 2024
36ebf2e
Add back `TreeUtils.fieldsFromTree`
jyoo980 May 19, 2024
06d74b1
Extracting delegation checking logic into `DelegationVisitor`
jyoo980 May 19, 2024
95c3a8f
Merge remote-tracking branch 'typetools/master' into yoo/standalone-d…
jyoo980 Jun 9, 2024
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
@@ -0,0 +1,33 @@
package org.checkerframework.common.delegation.qual;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* This is an annotation that indicates a field is a delegate, fields are not delegates by default.
*
* <p>Here is a way that this annotation may be used:
*
* <pre><code>
* class MyEnumeration&lt;T&gt; implements Enumeration&lt;T&gt; {
* {@literal @}Delegate
* private Enumeration&lt;T&gt; e;
*
* public boolean hasMoreElements() {
* return e.hasMoreElements();
* }
* }
* </code></pre>
*
* In the example above, {@code MyEnumeration.hasMoreElements()} delegates a call to {@code
* e.hasMoreElements()}.
*
* @checker_framework.manual #non-empty-checker Non-Empty Checker
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface Delegate {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.checkerframework.common.delegation.qual;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* This is an annotation that indicates a method that <i>must</i> be overridden in order for a
* conditional postcondition to hold for a delegating class.
*
* <p>Here is a way that this annotation may be used:
*
* <p>Given a class that declares a method with a postcondition annotation:
*
* <pre><code>
* class ArrayListVariant&lt;T&gt; {
* {@literal @}EnsuresPresentIf(result = true)
* {@literal @}DelegatorMustOverride
* public boolean hasMoreElements() {
* return e.hasMoreElements();
* }
* }
* </code></pre>
*
* A delegating client <i>must</i> override the method:
*
* <pre><code>
* class MyArrayListVariant&lt;T&gt; extends ArrayListVariant&lt;T&gt; {
*
* {@literal @}Delegate
* private ArrayListVariant&lt;T&gt; myList;
*
*
* {@literal @}Override
* {@literal @}EnsuresPresentIf(result = true)
* public boolean hasMoreElements() {
* return myList.hasMoreElements();
* }
* }
* </code></pre>
*
* Otherwise, a warning will be raised.
*
* @checker_framework.manual #non-empty-checker Non-Empty Checker
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface DelegatorMustOverride {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.checkerframework.common.delegation;

import java.lang.annotation.Annotation;
import java.util.Set;
import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.common.delegation.qual.Delegate;

/** Annotated type factory for the Delegation Checker. */
public class DelegationAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {

/** Create the type factory. */
public DelegationAnnotatedTypeFactory(BaseTypeChecker checker) {
super(checker);
this.postInit();
}

@Override
protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
return getBundledTypeQualifiers(Delegate.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.checkerframework.common.delegation;

import com.sun.source.tree.*;
import java.util.*;
import javax.lang.model.element.*;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.common.delegation.qual.Delegate;

/**
* This class enforces checks for the {@link Delegate} annotation.
*
* <p>It is not a checker for a type system. It enforces the following syntactic checks:
*
* <ul>
* <li>A class may have up to exactly one field marked with the {@link Delegate} annotation.
* <li>An overridden method's implementation must be exactly a call to the delegate field.
* <li>A class overrides <i>all</i> methods declared in its superclass.
* </ul>
*/
public class DelegationChecker extends BaseTypeChecker {}
Copy link
Member

Choose a reason for hiding this comment

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

This needs to extend SourceChecker.

Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
package org.checkerframework.common.delegation;

import com.sun.source.tree.BlockTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionStatementTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.StatementTree;
import com.sun.source.tree.ThrowTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.util.ElementFilter;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.common.basetype.BaseTypeVisitor;
import org.checkerframework.common.delegation.qual.Delegate;
import org.checkerframework.common.delegation.qual.DelegatorMustOverride;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;

public class DelegationVisitor extends BaseTypeVisitor<BaseAnnotatedTypeFactory> {

/** The maximum number of fields marked with {@link Delegate} permitted in a class. */
private final int MAX_NUM_DELEGATE_FIELDS = 1;

/** The field marked with {@link Delegate} for the current class. */
private @Nullable VariableTree delegate;

public DelegationVisitor(BaseTypeChecker checker) {
super(checker);
}

@Override
public void processClassTree(ClassTree tree) {
delegate = null; // Unset the previous delegate whenever a new class is visited
// TODO: what about inner classes?
List<VariableTree> delegates = getDelegateFields(tree);
if (delegates.size() > MAX_NUM_DELEGATE_FIELDS) {
VariableTree latestDelegate = delegates.get(delegates.size() - 1);
checker.reportError(latestDelegate, "multiple.delegate.annotations");
} else if (delegates.size() == 1) {
delegate = delegates.get(0);
checkSuperClassOverrides(tree);
}
// Do nothing if no delegate field is found
super.processClassTree(tree);
}

@Override
public void processMethodTree(MethodTree tree) {
super.processMethodTree(tree);
if (delegate == null || !isMarkedWithOverride(tree)) {
return;
}
MethodInvocationTree candidateDelegateCall = getLastExpression(tree.getBody());
boolean hasExceptionalExit =
hasExceptionalExit(tree.getBody(), UnsupportedOperationException.class);
if (hasExceptionalExit) {
return;
}
if (candidateDelegateCall == null) {
checker.reportWarning(tree, "invalid.delegate", tree.getName(), delegate.getName());
return;
}
Name enclosingMethodName = tree.getName();
if (!isValidDelegateCall(enclosingMethodName, candidateDelegateCall)) {
checker.reportWarning(tree, "invalid.delegate", tree.getName(), delegate.getName());
}
}

/**
* Return true if the given method call is a valid delegate call for the enclosing method.
*
* <p>A delegate method call must fulfill the following properties: its receiver must be the
* current field marked with {@link Delegate} in the class, and the name of the method call must
* match that of the enclosing method.
*
* @param enclosingMethodName the name of the enclosing method
* @param delegatedMethodCall the delegated method call
* @return true if the given method call is a valid delegate call for the enclosing method
*/
private boolean isValidDelegateCall(
Name enclosingMethodName, MethodInvocationTree delegatedMethodCall) {
assert delegate != null; // This method should only be invoked when delegate is non-null
ExpressionTree methodSelectTree = delegatedMethodCall.getMethodSelect();
MemberSelectTree fieldAccessTree = (MemberSelectTree) methodSelectTree;
VariableElement delegatedField = TreeUtils.asFieldAccess(fieldAccessTree.getExpression());
Name delegatedMethodName = TreeUtils.methodName(delegatedMethodCall);
// TODO: is there a better way to check? Comparing names seems fragile.
return enclosingMethodName.equals(delegatedMethodName)
&& delegatedField != null
&& delegatedField.getSimpleName().equals(delegate.getName());
}

/**
* Returns the fields of a class marked with a {@link Delegate} annotation.
*
* @param tree a class
* @return the fields of a class marked with a {@link Delegate} annotation
*/
private List<VariableTree> getDelegateFields(ClassTree tree) {
List<VariableTree> delegateFields = new ArrayList<>();
for (VariableTree field : TreeUtils.fieldsFromTree(tree)) {
List<AnnotationMirror> annosOnField =
TreeUtils.annotationsFromTypeAnnotationTrees(field.getModifiers().getAnnotations());
if (annosOnField.stream()
.anyMatch(anno -> atypeFactory.areSameByClass(anno, Delegate.class))) {
delegateFields.add(field);
}
}
return delegateFields;
}

/**
* Returns the last expression in a method body.
*
* <p>This method is used to identify a possible delegate method call. It will check whether a
* method has only one statement (a method invocation or a return statement), and return the
* expression that is associated with it. Otherwise, it will return null.
*
* @param tree the method body
* @return the last expression in the method body
*/
private @Nullable MethodInvocationTree getLastExpression(BlockTree tree) {
List<? extends StatementTree> stmts = tree.getStatements();
if (stmts.size() != 1) {
return null;
}
StatementTree stmt = stmts.get(0);
ExpressionTree lastExprInMethod = null;
if (stmt instanceof ExpressionStatementTree) {
lastExprInMethod = ((ExpressionStatementTree) stmt).getExpression();
} else if (stmt instanceof ReturnTree) {
lastExprInMethod = ((ReturnTree) stmt).getExpression();
}
if (!(lastExprInMethod instanceof MethodInvocationTree)) {
return null;
}
return (MethodInvocationTree) lastExprInMethod;
}

/**
* Return true if the last (and only) statement of the block throws an exception of the given
* class.
*
* @param tree a block tree
* @param clazz a class of exception (usually {@link UnsupportedOperationException})
* @return true if the last and only statement throws an exception of the given class
*/
private boolean hasExceptionalExit(BlockTree tree, Class<?> clazz) {
List<? extends StatementTree> stmts = tree.getStatements();
if (stmts.size() != 1) {
return false;
}
StatementTree lastStmt = stmts.get(0);
if (!(lastStmt instanceof ThrowTree)) {
return false;
}
ThrowTree throwStmt = (ThrowTree) lastStmt;
AnnotatedTypeMirror throwType = atypeFactory.getAnnotatedType(throwStmt.getExpression());
Class<?> exceptionClass = TypesUtils.getClassFromType(throwType.getUnderlyingType());
return exceptionClass.equals(clazz);
}

/**
* Validate whether a class overrides all declared methods in its superclass.
*
* <p>This is a basic implementation that naively checks whether all the superclass methods have
* been overridden by the subclass. It is unlikely in practice that a delegating subclass needs to
* override <i>all</i> the methods in a superclass for postconditions to hold.
*
* @param tree a class tree
*/
private void checkSuperClassOverrides(ClassTree tree) {
TypeElement classTreeElt = TreeUtils.elementFromDeclaration(tree);
if (classTreeElt == null || classTreeElt.getSuperclass() == null) {
return;
}
DeclaredType superClassMirror = (DeclaredType) classTreeElt.getSuperclass();
if (superClassMirror == null || superClassMirror.getKind() == TypeKind.NONE) {
return;
}
Set<Name> overriddenMethods =
getOverriddenMethods(tree).stream()
.map(ExecutableElement::getSimpleName)
.collect(Collectors.toSet());
Set<ExecutableElement> methodsDeclaredInSuperClass =
new HashSet<>(ElementFilter.methodsIn(superClassMirror.asElement().getEnclosedElements()));
Set<Name> methodsThatMustBeOverriden =
methodsDeclaredInSuperClass.stream()
.filter(e -> atypeFactory.getDeclAnnotation(e, DelegatorMustOverride.class) != null)
.map(ExecutableElement::getSimpleName)
.collect(Collectors.toSet());

// TODO: comparing a set of names isn't ideal, what about overloading?
if (!overriddenMethods.containsAll(methodsThatMustBeOverriden)) {
checker.reportWarning(
tree,
"delegate.override",
tree.getSimpleName(),
TypesUtils.getQualifiedName(superClassMirror));
}
}

/**
* Return a set of all methods in the class that are marked with {@link Override}.
*
* @param tree the class tree
* @return a set of all methods in the class that are marked with {@link Override}
*/
private Set<ExecutableElement> getOverriddenMethods(ClassTree tree) {
Set<ExecutableElement> overriddenMethods = new HashSet<>();
for (Tree member : tree.getMembers()) {
if (!(member instanceof MethodTree)) {
continue;
}
MethodTree method = (MethodTree) member;
if (isMarkedWithOverride(method)) {
overriddenMethods.add(TreeUtils.elementFromDeclaration(method));
}
}
return overriddenMethods;
}

/**
* Return true if a method is marked with {@link Override}.
*
* @param tree the method declaration
* @return true if the given method declaration is annotated with {@link Override}
*/
private boolean isMarkedWithOverride(MethodTree tree) {
Element method = TreeUtils.elementFromDeclaration(tree);
return atypeFactory.getDeclAnnotation(method, Override.class) != null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
multiple.delegate.annotations=A class may only have one @Delegate field
invalid.delegate=%s does not delegate a call to %s
delegate.override=%s does not override all methods in %s
Loading
Loading