-
Notifications
You must be signed in to change notification settings - Fork 425
Add Delegation Checker #6609
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
base: master
Are you sure you want to change the base?
Add Delegation Checker #6609
Changes from 174 commits
dddb8a3
f5b1188
d7e07e7
ce3cde3
61ce733
e20d5af
1a13283
93bc49e
433a993
5fe370d
53ef7e7
134aef7
cdf043d
eda22bb
8824de7
5aeea64
b9699e1
500c249
9e58eaf
c170355
1e66676
fc61d3d
e91c373
6fdfc3b
e7fd519
00714bc
4467854
20055de
98f218c
2c5e9ff
99b9242
f80abb2
db6dab9
89b4bee
a9964bc
19a2042
90de18e
a0da89d
ee28483
bca7308
a53b9ba
d788fe4
28859fb
266af5f
16d8aa1
aec4317
3fefdb4
e44ecb8
de84e5e
fefadec
feb14e2
10ddf62
92dee7b
a9b7069
cd8d5e1
f079ad5
5d5cf3a
a107fb2
2c4dc9b
6fc9073
0718311
f45e835
1c37e41
2a68280
b567d3c
ed46d30
d72f6bd
0660188
692b0c4
ac242c2
0c87474
3eceeda
649f386
f413c61
d67005d
8cb8fd3
2174fb7
5d472ae
349f825
82ec19e
ff710d5
3350681
6dbab9a
3c783c6
583bdab
f8b5c9f
471b30d
451b831
a3c967f
d3a3900
dfb58b9
44d0b51
1c7d8d4
e96fcf5
896b727
353b096
e6fe54a
c65f2fc
8cdcf6d
91f127a
b00303c
d0c3ae6
01fbe18
7156364
9a13edc
074f638
3a782c0
44c72dd
e385d0e
2b3cd4b
1ba6043
c3478c2
25ad3c4
c044594
7b70a61
3a5c8db
6d8dcc5
2618787
d08e2ec
22cd54d
9e1028d
4a18b42
3fda942
a945675
b7d7e17
613986d
763df84
eafc977
7103274
bb26689
4264e4d
ef1eec2
6a89910
4d09502
5b2056e
7c3527d
50bfa36
b5adf0d
a0abd2f
05b15d0
b1ed401
f8ba782
f5b1614
21e21f2
38c5402
fb2bb56
7d695da
75fd8fa
7fa7d4a
a8248cc
9887291
8e91f0d
e7906b0
14c7977
0478e43
6d65157
89d3861
fa1539c
0f53325
9a67aab
46a2725
572584d
c0c04a0
64c469b
615205d
6654033
878ffeb
6ec0516
5c65841
afe12cc
87f8e0e
36ebf2e
06d74b1
95c3a8f
cea5947
8305d21
4362a11
ddf2b3b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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<T> implements Enumeration<T> { | ||
| * {@literal @}Delegate | ||
| * private Enumeration<T> 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<T> { | ||
| * {@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<T> extends ArrayListVariant<T> { | ||
| * | ||
| * {@literal @}Delegate | ||
| * private ArrayListVariant<T> 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.*; | ||
mernst marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| import javax.lang.model.element.*; | ||
mernst marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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 {} | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs to extend |
||
| 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? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
mernst marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
mernst marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * 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? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+201
to
+213
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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? | |
| Set<String> overriddenMethods = | |
| getOverriddenMethods(tree).stream() | |
| .map(ExecutableElement::toString) | |
| .collect(Collectors.toSet()); | |
| Set<ExecutableElement> methodsDeclaredInSuperClass = | |
| new HashSet<>(ElementFilter.methodsIn(superClassMirror.asElement().getEnclosedElements())); | |
| Set<String> methodsThatMustBeOverriden = | |
| methodsDeclaredInSuperClass.stream() | |
| .filter(e -> atypeFactory.getDeclAnnotation(e, DelegatorMustOverride.class) != null) | |
| .map(ExecutableElement::toString) | |
| .collect(Collectors.toSet()); | |
| // Compare full method signatures to handle overloading |
| 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 |
Uh oh!
There was an error while loading. Please reload this page.