| package org.checkerframework.checker.guieffect; |
| |
| import com.sun.source.tree.AssignmentTree; |
| import com.sun.source.tree.ClassTree; |
| import com.sun.source.tree.ExpressionTree; |
| import com.sun.source.tree.LambdaExpressionTree; |
| import com.sun.source.tree.MethodInvocationTree; |
| import com.sun.source.tree.MethodTree; |
| import com.sun.source.tree.NewClassTree; |
| import com.sun.source.tree.ReturnTree; |
| import com.sun.source.tree.Tree; |
| import com.sun.source.tree.VariableTree; |
| import com.sun.source.util.TreePath; |
| import java.util.ArrayDeque; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import javax.lang.model.element.AnnotationMirror; |
| import javax.lang.model.element.ExecutableElement; |
| import javax.lang.model.element.TypeElement; |
| import javax.lang.model.element.VariableElement; |
| import javax.lang.model.type.DeclaredType; |
| import org.checkerframework.checker.guieffect.qual.AlwaysSafe; |
| import org.checkerframework.checker.guieffect.qual.PolyUI; |
| import org.checkerframework.checker.guieffect.qual.PolyUIEffect; |
| import org.checkerframework.checker.guieffect.qual.PolyUIType; |
| import org.checkerframework.checker.guieffect.qual.SafeEffect; |
| import org.checkerframework.checker.guieffect.qual.UI; |
| import org.checkerframework.checker.guieffect.qual.UIEffect; |
| import org.checkerframework.common.basetype.BaseTypeChecker; |
| import org.checkerframework.common.basetype.BaseTypeVisitor; |
| import org.checkerframework.framework.type.AnnotatedTypeFactory.ParameterizedExecutableType; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType; |
| import org.checkerframework.framework.util.AnnotatedTypes; |
| import org.checkerframework.javacutil.AnnotationBuilder; |
| import org.checkerframework.javacutil.ElementUtils; |
| import org.checkerframework.javacutil.Pair; |
| import org.checkerframework.javacutil.TreePathUtil; |
| import org.checkerframework.javacutil.TreeUtils; |
| import org.checkerframework.javacutil.TypesUtils; |
| |
| /** Require that only UI code invokes code with the UI effect. */ |
| public class GuiEffectVisitor extends BaseTypeVisitor<GuiEffectTypeFactory> { |
| |
| protected final boolean debugSpew; |
| |
| // effStack and currentMethods should always be the same size. |
| protected final ArrayDeque<Effect> effStack; |
| protected final ArrayDeque<MethodTree> currentMethods; |
| |
| public GuiEffectVisitor(BaseTypeChecker checker) { |
| super(checker); |
| debugSpew = checker.getLintOption("debugSpew", false); |
| if (debugSpew) { |
| System.err.println("Running GuiEffectVisitor"); |
| } |
| effStack = new ArrayDeque<>(); |
| currentMethods = new ArrayDeque<>(); |
| } |
| |
| @Override |
| protected GuiEffectTypeFactory createTypeFactory() { |
| return new GuiEffectTypeFactory(checker, debugSpew); |
| } |
| |
| // The issue is that the receiver implicitly receives an @AlwaysSafe anno, so calls on @UI |
| // references fail because the framework doesn't implicitly upcast the receiver (which in |
| // general wouldn't be sound). |
| // TODO: Fix method receiver defaults: method-polymorphic for any polymorphic method, UI |
| // for any UI instantiations, safe otherwise |
| @Override |
| protected void checkMethodInvocability( |
| AnnotatedExecutableType method, MethodInvocationTree node) { |
| // The inherited version of this complains about invoking methods of @UI instantiations of |
| // classes, which by default are annotated @AlwaysSafe, which for data type qualifiers is |
| // reasonable, but it not what we want, since we want . |
| // TODO: Undo this hack! |
| } |
| |
| protected class GuiEffectOverrideChecker extends OverrideChecker { |
| /** |
| * Extend the receiver part of the method override check. We extend the standard check, to |
| * additionally permit narrowing the receiver's permission to {@code @AlwaysSafe} in a safe |
| * instantiation of a {@code @PolyUIType}. Returns true if the override is permitted. |
| */ |
| @Override |
| protected boolean checkReceiverOverride() { |
| // We cannot reuse the inherited method because it directly issues the failure, but we |
| // want a more permissive check. So this is copied down and modified from |
| // BaseTypeVisitor.OverrideChecker.checkReceiverOverride. |
| // isSubtype() requires its arguments to be actual subtypes with |
| // respect to JLS, but the overrider receiver is not a subtype of the |
| // overridden receiver. Hence copying the annotations. |
| // TODO: Does this need to be improved for generic receivers? I.e., do we need to |
| // add extra checking to reject the case of also changing qualifiers in type parameters? |
| // Such as overriding a {@code @PolyUI C<@UI T>} by {@code @AlwaysSafe C<@AlwaysSafe |
| // T>}? The change to the receiver permission is acceptable, while the change to the |
| // parameter should be rejected. |
| AnnotatedTypeMirror overriddenReceiver = |
| overrider.getReceiverType().getErased().shallowCopy(false); |
| overriddenReceiver.addAnnotations(overridden.getReceiverType().getAnnotations()); |
| if (!atypeFactory |
| .getTypeHierarchy() |
| .isSubtype(overriddenReceiver, overrider.getReceiverType().getErased())) { |
| // This is the point at which the default check would issue an error. |
| // We additionally permit overrides to move from @PolyUI receivers to @AlwaysSafe |
| // receivers, if it's in a @AlwaysSafe specialization of a @PolyUIType |
| boolean safeParent = overriddenType.getAnnotation(AlwaysSafe.class) != null; |
| boolean polyParentDecl = |
| atypeFactory.getDeclAnnotation( |
| overriddenType.getUnderlyingType().asElement(), PolyUIType.class) |
| != null; |
| // TODO: How much validation do I need here? Do I need to check that the overridden |
| // receiver was really @PolyUI and the method is really an @PolyUIEffect? I don't think so |
| // - we know it's a polymorphic parent type, so all receivers would be @PolyUI. |
| // Java would already reject before running type annotation processors if the Java |
| // types were wrong. |
| // The *only* extra leeway we want to permit is overriding @PolyUI receiver to |
| // @AlwaysSafe. But with generics, the tentative check below is inadequate. |
| boolean safeReceiverOverride = |
| overrider.getReceiverType().getAnnotation(AlwaysSafe.class) != null; |
| if (safeParent && polyParentDecl && safeReceiverOverride) { |
| return true; |
| } |
| checker.reportError( |
| overriderTree, |
| "override.receiver", |
| overrider.getReceiverType(), |
| overridden.getReceiverType(), |
| overriderType, |
| overrider, |
| overriddenType, |
| overridden); |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Create a GuiEffectOverrideChecker. |
| * |
| * @param overriderTree the AST node of the overriding method or method reference |
| * @param overrider the type of the overriding method |
| * @param overridingType the type enclosing the overrider method, usually an |
| * AnnotatedDeclaredType; for Method References may be something else |
| * @param overridingReturnType the return type of the overriding method |
| * @param overridden the type of the overridden method |
| * @param overriddenType the declared type enclosing the overridden method |
| * @param overriddenReturnType the return type of the overridden method |
| */ |
| public GuiEffectOverrideChecker( |
| Tree overriderTree, |
| AnnotatedExecutableType overrider, |
| AnnotatedTypeMirror overridingType, |
| AnnotatedTypeMirror overridingReturnType, |
| AnnotatedExecutableType overridden, |
| AnnotatedDeclaredType overriddenType, |
| AnnotatedTypeMirror overriddenReturnType) { |
| super( |
| overriderTree, |
| overrider, |
| overridingType, |
| overridingReturnType, |
| overridden, |
| overriddenType, |
| overriddenReturnType); |
| } |
| } |
| |
| @Override |
| protected OverrideChecker createOverrideChecker( |
| Tree overriderTree, |
| AnnotatedExecutableType overrider, |
| AnnotatedTypeMirror overridingType, |
| AnnotatedTypeMirror overridingReturnType, |
| AnnotatedExecutableType overridden, |
| AnnotatedTypeMirror.AnnotatedDeclaredType overriddenType, |
| AnnotatedTypeMirror overriddenReturnType) { |
| return new GuiEffectOverrideChecker( |
| overriderTree, |
| overrider, |
| overridingType, |
| overridingReturnType, |
| overridden, |
| overriddenType, |
| overriddenReturnType); |
| } |
| |
| @Override |
| protected Set<? extends AnnotationMirror> getExceptionParameterLowerBoundAnnotations() { |
| return Collections.singleton(AnnotationBuilder.fromClass(elements, AlwaysSafe.class)); |
| } |
| |
| @Override |
| public boolean isValidUse( |
| AnnotatedTypeMirror.AnnotatedDeclaredType declarationType, |
| AnnotatedTypeMirror.AnnotatedDeclaredType useType, |
| Tree tree) { |
| boolean ret = |
| useType.hasAnnotation(AlwaysSafe.class) |
| || useType.hasAnnotation(PolyUI.class) |
| || atypeFactory.isPolymorphicType( |
| (TypeElement) declarationType.getUnderlyingType().asElement()) |
| || (useType.hasAnnotation(UI.class) && declarationType.hasAnnotation(UI.class)); |
| if (debugSpew && !ret) { |
| System.err.println("use: " + useType); |
| System.err.println("use safe: " + useType.hasAnnotation(AlwaysSafe.class)); |
| System.err.println("use poly: " + useType.hasAnnotation(PolyUI.class)); |
| System.err.println("use ui: " + useType.hasAnnotation(UI.class)); |
| System.err.println("declaration safe: " + declarationType.hasAnnotation(AlwaysSafe.class)); |
| System.err.println( |
| "declaration poly: " |
| + atypeFactory.isPolymorphicType( |
| (TypeElement) declarationType.getUnderlyingType().asElement())); |
| System.err.println("declaration ui: " + declarationType.hasAnnotation(UI.class)); |
| System.err.println("declaration: " + declarationType); |
| } |
| return ret; |
| } |
| |
| @Override |
| @SuppressWarnings("interning:not.interned") // comparing AST nodes |
| public Void visitLambdaExpression(LambdaExpressionTree node, Void p) { |
| Void v = super.visitLambdaExpression(node, p); |
| // If this is a lambda inferred to be @UI, scan up the path and re-check any assignments |
| // involving it. |
| if (atypeFactory.isDirectlyMarkedUIThroughInference(node)) { |
| // Backtrack path to the lambda expression itself |
| TreePath path = visitorState.getPath(); |
| while (path.getLeaf() != node) { |
| assert path.getLeaf().getKind() != Tree.Kind.COMPILATION_UNIT; |
| path = path.getParentPath(); |
| } |
| scanUp(path); |
| } |
| return v; |
| } |
| |
| @Override |
| protected void checkExtendsImplements(ClassTree classTree) { |
| // Skip this check |
| } |
| |
| @Override |
| protected void checkConstructorResult( |
| AnnotatedExecutableType constructorType, ExecutableElement constructorElement) { |
| // Skip this check. |
| } |
| |
| @Override |
| protected void checkForPolymorphicQualifiers(ClassTree classTree) { |
| // Polymorphic qualifiers are legal on classes, so skip this check. |
| } |
| |
| // Check that the invoked effect is <= permitted effect (effStack.peek()) |
| @Override |
| public Void visitMethodInvocation(MethodInvocationTree node, Void p) { |
| if (debugSpew) { |
| System.err.println("For invocation " + node + " in " + currentMethods.peek().getName()); |
| } |
| |
| // Target method annotations |
| ExecutableElement methodElt = TreeUtils.elementFromUse(node); |
| if (debugSpew) { |
| System.err.println("methodElt found"); |
| } |
| |
| Tree callerTree = TreePathUtil.enclosingMethodOrLambda(getCurrentPath()); |
| if (callerTree == null) { |
| // Static initializer; let's assume this is safe to have the UI effect |
| if (debugSpew) { |
| System.err.println("No enclosing method: likely static initializer"); |
| } |
| return super.visitMethodInvocation(node, p); |
| } |
| if (debugSpew) { |
| System.err.println("callerTree found: " + callerTree.getKind()); |
| } |
| |
| Effect targetEffect = |
| atypeFactory.getComputedEffectAtCallsite(node, visitorState.getMethodReceiver(), methodElt); |
| |
| Effect callerEffect = null; |
| if (callerTree.getKind() == Tree.Kind.METHOD) { |
| ExecutableElement callerElt = TreeUtils.elementFromDeclaration((MethodTree) callerTree); |
| if (debugSpew) { |
| System.err.println("callerElt found"); |
| } |
| |
| callerEffect = atypeFactory.getDeclaredEffect(callerElt); |
| final DeclaredType callerReceiverType = this.visitorState.getClassType().getUnderlyingType(); |
| assert callerReceiverType != null; |
| final TypeElement callerReceiverElt = (TypeElement) callerReceiverType.asElement(); |
| // Note: All these checks should be fast in the common case, but happen for every method call |
| // inside the anonymous class. Consider a cache here if profiling surfaces this as taking too |
| // long. |
| if (TypesUtils.isAnonymous(callerReceiverType) |
| // Skip if already inferred @UI |
| && !effStack.peek().isUI() |
| // Ignore if explicitly annotated |
| && !atypeFactory.fromElement(callerReceiverElt).hasAnnotation(AlwaysSafe.class) |
| && !atypeFactory.fromElement(callerReceiverElt).hasAnnotation(UI.class)) { |
| boolean overridesPolymorphic = false; |
| Map<AnnotatedTypeMirror.AnnotatedDeclaredType, ExecutableElement> overriddenMethods = |
| AnnotatedTypes.overriddenMethods(elements, atypeFactory, callerElt); |
| for (Map.Entry<AnnotatedTypeMirror.AnnotatedDeclaredType, ExecutableElement> pair : |
| overriddenMethods.entrySet()) { |
| AnnotatedTypeMirror.AnnotatedDeclaredType overriddenType = pair.getKey(); |
| AnnotatedExecutableType overriddenMethod = |
| AnnotatedTypes.asMemberOf(types, atypeFactory, overriddenType, pair.getValue()); |
| if (atypeFactory.getDeclAnnotation(overriddenMethod.getElement(), PolyUIEffect.class) |
| != null |
| && atypeFactory.getDeclAnnotation( |
| overriddenType.getUnderlyingType().asElement(), PolyUIType.class) |
| != null) { |
| overridesPolymorphic = true; |
| break; |
| } |
| } |
| // Perform anonymous class polymorphic effect inference: |
| // method overrides @PolyUIEffect method of @PolyUIClass class, calls @UIEffect => |
| // @UI anon class |
| if (overridesPolymorphic && targetEffect.isUI()) { |
| // Mark the anonymous class as @UI |
| atypeFactory.constrainAnonymousClassToUI(callerReceiverElt); |
| // Then re-calculate this method's effect (it might still not be an |
| // @PolyUIEffect method). |
| callerEffect = atypeFactory.getDeclaredEffect(callerElt); |
| effStack.pop(); |
| effStack.push(callerEffect); |
| } |
| } |
| |
| // Field initializers inside anonymous inner classes show up with a null current-method |
| // --- the traversal goes straight from the class to the initializer. |
| assert (currentMethods.peek() == null || callerEffect.equals(effStack.peek())); |
| } else if (callerTree.getKind() == Tree.Kind.LAMBDA_EXPRESSION) { |
| callerEffect = |
| atypeFactory.getInferedEffectForLambdaExpression((LambdaExpressionTree) callerTree); |
| // Perform lambda polymorphic effect inference: @PolyUI lambda, calling @UIEffect => @UI |
| // lambda |
| if (targetEffect.isUI() && callerEffect.isPoly()) { |
| atypeFactory.constrainLambdaToUI((LambdaExpressionTree) callerTree); |
| callerEffect = new Effect(UIEffect.class); |
| } |
| } |
| assert callerEffect != null; |
| |
| if (!Effect.lessThanOrEqualTo(targetEffect, callerEffect)) { |
| checker.reportError(node, "call.ui", targetEffect, callerEffect); |
| if (debugSpew) { |
| System.err.println("Issuing error for node: " + node); |
| } |
| } |
| if (debugSpew) { |
| System.err.println("Successfully finished main non-recursive checkinv of invocation " + node); |
| } |
| return super.visitMethodInvocation(node, p); |
| } |
| |
| @Override |
| public Void visitMethod(MethodTree node, Void p) { |
| // TODO: If the type we're in is a polymorphic (over effect qualifiers) type, the receiver must |
| // be @PolyUI. Otherwise a "non-polymorphic" method of a polymorphic type could be called on a |
| // UI instance, which then gets a Safe reference to itself (unsound!) that it can then pass off |
| // elsewhere (dangerous!). So all receivers in methods of a @PolyUIType must be @PolyUI. |
| |
| // TODO: What do we do then about classes that inherit from a concrete instantiation? If it |
| // subclasses a Safe instantiation, all is well. If it subclasses a UI instantiation, then the |
| // receivers should probably be @UI in both new and override methods, so calls to polymorphic |
| // methods of the parent class will work correctly. In which case for proving anything, the |
| // qualifier on sublasses of UI instantiations would always have to be @UI... Need to write down |
| // |- t for this system! And the judgments for method overrides and inheritance! Those are |
| // actually the hardest part of the system. |
| |
| ExecutableElement methElt = TreeUtils.elementFromDeclaration(node); |
| if (debugSpew) { |
| System.err.println("Visiting method " + methElt + " of " + methElt.getEnclosingElement()); |
| } |
| |
| // Check for conflicting (multiple) annotations |
| assert (methElt != null); |
| // TypeMirror scratch = methElt.getReturnType(); |
| AnnotationMirror targetUIP = atypeFactory.getDeclAnnotation(methElt, UIEffect.class); |
| AnnotationMirror targetSafeP = atypeFactory.getDeclAnnotation(methElt, SafeEffect.class); |
| AnnotationMirror targetPolyP = atypeFactory.getDeclAnnotation(methElt, PolyUIEffect.class); |
| TypeElement targetClassElt = (TypeElement) methElt.getEnclosingElement(); |
| |
| if ((targetUIP != null && (targetSafeP != null || targetPolyP != null)) |
| || (targetSafeP != null && targetPolyP != null)) { |
| checker.reportError(node, "annotations.conflicts"); |
| } |
| if (targetPolyP != null && !atypeFactory.isPolymorphicType(targetClassElt)) { |
| checker.reportError(node, "polymorphism"); |
| } |
| if (targetUIP != null && atypeFactory.isUIType(targetClassElt)) { |
| checker.reportWarning(node, "effects.redundant.uitype"); |
| } |
| |
| // TODO: Report an error for polymorphic method bodies??? Until we fix the receiver |
| // defaults, it won't really be correct |
| @SuppressWarnings("unused") // call has side effects |
| Effect.EffectRange range = |
| atypeFactory.findInheritedEffectRange( |
| ((TypeElement) methElt.getEnclosingElement()), methElt, true, node); |
| // if (targetUIP == null && targetSafeP == null && targetPolyP == null) { |
| // implicitly annotate this method with the LUB of the effects of the methods it overrides |
| // atypeFactory.fromElement(methElt).addAnnotation(range != null ? range.min.getAnnot() |
| // : (isUIType(((TypeElement)methElt.getEnclosingElement())) ? UI.class : |
| // AlwaysSafe.class)); |
| // TODO: This line does nothing! AnnotatedTypeMirror.addAnnotation |
| // silently ignores non-qualifier annotations! |
| // System.err.println("ERROR: TREE ANNOTATOR SHOULD HAVE ADDED EXPLICIT ANNOTATION! (" |
| // +node.getName()+")"); |
| // atypeFactory |
| // .fromElement(methElt) |
| // .addAnnotation(atypeFactory.getDeclaredEffect(methElt).getAnnot()); |
| // } |
| |
| // We hang onto the current method here for ease. We back up the old |
| // current method because this code is reentrant when we traverse methods of an inner class |
| currentMethods.addFirst(node); |
| // effStack.push(targetSafeP != null ? new Effect(AlwaysSafe.class) : |
| // (targetPolyP != null ? new Effect(PolyUI.class) : |
| // (targetUIP != null ? new Effect(UI.class) : |
| // (range != null ? range.min : |
| // (isUIType(((TypeElement)methElt.getEnclosingElement())) ? new Effect(UI.class) : new |
| // Effect(AlwaysSafe.class)))))); |
| effStack.addFirst(atypeFactory.getDeclaredEffect(methElt)); |
| if (debugSpew) { |
| System.err.println("Pushing " + effStack.peek() + " onto the stack when checking " + methElt); |
| } |
| |
| Void ret = super.visitMethod(node, p); |
| currentMethods.removeFirst(); |
| effStack.removeFirst(); |
| return ret; |
| } |
| |
| @Override |
| @SuppressWarnings("interning:not.interned") // comparing AST nodes |
| public Void visitNewClass(NewClassTree node, Void p) { |
| Void v = super.visitNewClass(node, p); |
| // If this is an anonymous inner class inferred to be @UI, scan up the path and re-check any |
| // assignments involving it. |
| if (atypeFactory.isDirectlyMarkedUIThroughInference(node)) { |
| // Backtrack path to the new class expression itself |
| TreePath path = visitorState.getPath(); |
| while (path.getLeaf() != node) { |
| assert path.getLeaf().getKind() != Tree.Kind.COMPILATION_UNIT; |
| path = path.getParentPath(); |
| } |
| scanUp(visitorState.getPath().getParentPath()); |
| } |
| return v; |
| } |
| |
| /** |
| * This method is called to traverse the path back up from any anonymous inner class or lambda |
| * which has been inferred to be UI affecting and re-run {@code commonAssignmentCheck} as needed |
| * on places where the class declaration or lambda expression are being assigned to a variable, |
| * passed as a parameter or returned from a method. This is necessary because the normal visitor |
| * traversal only checks assignments on the way down the AST, before inference has had a chance to |
| * run. |
| * |
| * @param path the path to traverse up from a UI-affecting class |
| */ |
| private void scanUp(TreePath path) { |
| Tree tree = path.getLeaf(); |
| switch (tree.getKind()) { |
| case ASSIGNMENT: |
| AssignmentTree assignmentTree = (AssignmentTree) tree; |
| commonAssignmentCheck( |
| atypeFactory.getAnnotatedType(assignmentTree.getVariable()), |
| atypeFactory.getAnnotatedType(assignmentTree.getExpression()), |
| assignmentTree.getExpression(), |
| "assignment"); |
| break; |
| case VARIABLE: |
| VariableTree variableTree = (VariableTree) tree; |
| commonAssignmentCheck( |
| atypeFactory.getAnnotatedType(variableTree), |
| atypeFactory.getAnnotatedType(variableTree.getInitializer()), |
| variableTree.getInitializer(), |
| "assignment"); |
| break; |
| case METHOD_INVOCATION: |
| MethodInvocationTree invocationTree = (MethodInvocationTree) tree; |
| List<? extends ExpressionTree> args = invocationTree.getArguments(); |
| ParameterizedExecutableType mType = atypeFactory.methodFromUse(invocationTree); |
| AnnotatedExecutableType invokedMethod = mType.executableType; |
| ExecutableElement method = invokedMethod.getElement(); |
| CharSequence methodName = ElementUtils.getSimpleNameOrDescription(method); |
| List<? extends VariableElement> methodParams = method.getParameters(); |
| List<AnnotatedTypeMirror> argsTypes = |
| AnnotatedTypes.expandVarArgs( |
| atypeFactory, invokedMethod, invocationTree.getArguments()); |
| for (int i = 0; i < args.size(); ++i) { |
| if (args.get(i).getKind() == Tree.Kind.NEW_CLASS |
| || args.get(i).getKind() == Tree.Kind.LAMBDA_EXPRESSION) { |
| commonAssignmentCheck( |
| argsTypes.get(i), |
| atypeFactory.getAnnotatedType(args.get(i)), |
| args.get(i), |
| "argument", |
| methodParams.get(i), |
| methodName); |
| } |
| } |
| break; |
| case RETURN: |
| ReturnTree returnTree = (ReturnTree) tree; |
| if (returnTree.getExpression().getKind() == Tree.Kind.NEW_CLASS |
| || returnTree.getExpression().getKind() == Tree.Kind.LAMBDA_EXPRESSION) { |
| Tree enclosing = TreePathUtil.enclosingMethodOrLambda(path); |
| AnnotatedTypeMirror ret = null; |
| if (enclosing.getKind() == Tree.Kind.METHOD) { |
| MethodTree enclosingMethod = (MethodTree) enclosing; |
| boolean valid = validateTypeOf(enclosing); |
| if (valid) { |
| ret = atypeFactory.getMethodReturnType(enclosingMethod, returnTree); |
| } |
| } else { |
| ret = |
| atypeFactory |
| .getFunctionTypeFromTree((LambdaExpressionTree) enclosing) |
| .getReturnType(); |
| } |
| |
| if (ret != null) { |
| Pair<Tree, AnnotatedTypeMirror> preAssignmentContext = |
| visitorState.getAssignmentContext(); |
| try { |
| visitorState.setAssignmentContext(Pair.of((Tree) returnTree, ret)); |
| commonAssignmentCheck( |
| ret, |
| atypeFactory.getAnnotatedType(returnTree.getExpression()), |
| returnTree.getExpression(), |
| "return"); |
| } finally { |
| visitorState.setAssignmentContext(preAssignmentContext); |
| } |
| } |
| } |
| break; |
| case METHOD: |
| // Stop scanning at method boundaries, since the expression can't escape the method |
| // without either being assigned to a field or returned. |
| return; |
| case CLASS: |
| // Can't ever happen, because we stop scanning at either method or field initializer |
| // boundaries |
| assert false; |
| return; |
| default: |
| scanUp(path.getParentPath()); |
| } |
| } |
| |
| // @Override |
| // public Void visitMemberSelect(MemberSelectTree node, Void p) { |
| // TODO: Same effect checks as for methods |
| // return super.visitMemberSelect(node, p); |
| // } |
| |
| // @Override |
| // public void processClassTree(ClassTree node) { |
| // TODO: Check constraints on this class decl vs. parent class decl., and interfaces |
| // TODO: This has to wait for now: maybe this will be easier with the isValidUse on the |
| // TypeFactory. |
| // AnnotatedTypeMirror.AnnotatedDeclaredType atype = atypeFactory.fromClass(node); |
| |
| // Push a null method and UI effect onto the stack for static field initialization |
| // TODO: Figure out if this is safe! For static data, almost certainly, |
| // but for statically initialized instance fields, I'm assuming those |
| // are implicitly moved into each constructor, which must then be @UI. |
| // currentMethods.addFirst(null); |
| // effStack.addFirst(new Effect(UIEffect.class)); |
| // super.processClassTree(node); |
| // currentMethods.removeFirst(); |
| // effStack.removeFirst(); |
| // } |
| } |