All Downloads are FREE. Search and download functionalities are using the official Maven repository.

checker.src.org.checkerframework.checker.guieffect.GuiEffectTypeFactory Maven / Gradle / Ivy

Go to download

The Checker Framework enhances Java’s type system to make it more powerful and useful. This lets software developers detect and prevent errors in their Java programs. The Checker Framework includes compiler plug-ins ("checkers") that find bugs or verify their absence. It also permits you to write your own compiler plug-ins.

There is a newer version: 3.42.0
Show newest version
package org.checkerframework.checker.guieffect;

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.SafeType;
import org.checkerframework.checker.guieffect.qual.UI;
import org.checkerframework.checker.guieffect.qual.UIEffect;
import org.checkerframework.checker.guieffect.qual.UIPackage;
import org.checkerframework.checker.guieffect.qual.UIType;
import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.framework.source.Result;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.TypesUtils;

import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;

import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;

/**
 * Annotated type factory for the GUI Effect Checker.
 */
public class GuiEffectTypeFactory extends BaseAnnotatedTypeFactory {

    protected final boolean debugSpew;

    public GuiEffectTypeFactory(BaseTypeChecker checker, boolean spew) {
        // use true to enable flow inference, false to disable it
        super(checker, false);

        debugSpew = spew;
        this.postInit();
    }

    // Could move this to a public method on the checker class
    public ExecutableElement findJavaOverride(ExecutableElement overrider, TypeMirror parentType) {
        if (parentType.getKind() != TypeKind.NONE) {
            if (debugSpew) {
                System.err.println("Searching for overridden methods from " + parentType);
            }

            TypeElement overriderClass = (TypeElement)overrider.getEnclosingElement();
            TypeElement elem = (TypeElement)((DeclaredType)parentType).asElement();
            if (debugSpew) {
                System.err.println("necessary TypeElements acquired: " + elem);
            }

            for (Element e : elem.getEnclosedElements()) {
                if (debugSpew) {
                    System.err.println("Considering element "+e);
                }
                if (e.getKind() == ElementKind.METHOD || e.getKind() == ElementKind.CONSTRUCTOR) {
                    ExecutableElement ex = (ExecutableElement)e;
                    boolean overrides = elements.overrides(overrider, ex, overriderClass);
                    if (overrides) {
                        return ex;
                    }
                }
            }
            if (debugSpew) {
                System.err.println("Done considering elements of " + parentType);
            }
        }
        return null;
    }

    public boolean isPolymorphicType(TypeElement cls) {
        assert (cls != null);
        return getDeclAnnotation(cls, PolyUIType.class) != null ||
                fromElement(cls).hasAnnotation(PolyUI.class);
    }

    public boolean isUIType(TypeElement cls) {
        if (debugSpew) {
            System.err.println(" isUIType("+cls+")");
        }
        boolean targetClassUIP = fromElement(cls).hasAnnotation(UI.class);
        AnnotationMirror targetClassUITypeP = getDeclAnnotation(cls, UIType.class);
        AnnotationMirror targetClassSafeTypeP = getDeclAnnotation(cls, SafeType.class);

        if (targetClassSafeTypeP != null) {
            return false; // explicitly marked not a UI type
        }

        boolean hasUITypeDirectly = (targetClassUIP || targetClassUITypeP != null);

        if (hasUITypeDirectly) {
            return true;
        }

        // Anon inner classes should not inherit the package annotation, since
        // they're so often used for closures to run async on background
        // threads.
        if (isAnonymousType(cls)) {
            return false;
        }

        // We don't check polymorphic annos so we can make a couple methods of
        // an @UIType polymorphic explicitly
        // AnnotationMirror targetClassPolyP = getDeclAnnotation(cls, PolyUI.class);
        // AnnotationMirror targetClassPolyTypeP = getDeclAnnotation(cls, PolyUIType.class);
        boolean targetClassSafeP = fromElement(cls).hasAnnotation(AlwaysSafe.class);
        if (targetClassSafeP) {
            return false; // explicitly annotated otherwise
        }

        // Look for the package
        Element packageP = ElementUtils.enclosingPackage(cls);

        if (packageP != null) {
            if (debugSpew) {
                System.err.println("Found package " + packageP);
            }
            if (getDeclAnnotation(packageP, UIPackage.class) != null) {
                if (debugSpew) {
                    System.err.println("Package " + packageP + " is annotated @UIPackage");
                }
                return true;
            }
        }

        return false;
    }

    // TODO: is there a framework method for this?
    private static boolean isAnonymousType(TypeElement elem) {
        return elem.getSimpleName().length() == 0;
    }

    /*
     * Calling context annotations
       To make anon-inner-classes work, I need to climb the inheritance DAG, until I:
       - find the class/interface that declares this calling method (an anon inner class is a separate class that implements an interface)
       - check whether *that* declaration specifies @UI on either the type or method
       A method has the UI effect when:
       1. A method is UI if annotated @UIEffect
       2. A method is UI if the enclosing class is annotated @UI or @UIType and the method is not annotated @AlwaysSafe
       3. A method is UI if the corresponding method in the super-class/interface is UI,
          and this method is not annotated @AlwaysSafe, and this method resides in an anonymous inner class (named classes still require
          a package/class/method annotation to make it UI, only anon inner classes have this inheritance-by-default)
          + A method must be *annotated* UI if the method it overrides is *annotated* UI
          + A method must be *annotated* UI if it overrides a UI method and the enclosing class is not UI
       4. It is an error if a method is UI but the same method in a super-type is not UI
       5. It is an error if two super-types specify the same method, where one type says it's UI and one says it's not
          (it's possible to simply enforce the weaker (safe) effect, but this seems more principled, it's easier ---
          backwards-compatible --- to change our minds about this later)
    */
    public Effect getDeclaredEffect(ExecutableElement methodElt) {
        if (debugSpew) {
            System.err.println("begin mayHaveUIEffect(" + methodElt + ")");
        }
        AnnotationMirror targetUIP = getDeclAnnotation(methodElt, UIEffect.class);
        AnnotationMirror targetSafeP = getDeclAnnotation(methodElt, SafeEffect.class);
        AnnotationMirror targetPolyP = getDeclAnnotation(methodElt, PolyUIEffect.class);
        TypeElement targetClassElt = (TypeElement)methodElt.getEnclosingElement();

        if (debugSpew) {
            System.err.println("targetClassElt found");
        }

        // Short-circuit if the method is explicitly annotated
        if (targetSafeP != null) {
            if (debugSpew) {
                System.err.println("Method marked @SafeEffect");
            }
            return new Effect(SafeEffect.class);
        } else if (targetUIP != null) {
            if (debugSpew) {
                System.err.println("Method marked @UIEffect");
            }
            return new Effect(UIEffect.class);
        } else if (targetPolyP != null) {
            if (debugSpew) {
                System.err.println("Method marked @PolyUIEffect");
            }
            return new Effect(PolyUIEffect.class);
        }

        // The method is not explicitly annotated, so check class and package annotations,
        // and supertype effects if in an anonymous inner class

        if (isUIType(targetClassElt)) {
            // Already checked, no explicit @SafeEffect annotation
            return new Effect(UIEffect.class);
        }

        // Anonymous inner types should just get the effect of the parent by
        // default, rather than annotating every instance. Unless it's
        // implementing a polymorphic supertype, in which case we still want the
        // developer to be explicit.
        if (isAnonymousType(targetClassElt)) {
            boolean canInheritParentEffects = true; // Refine this for polymorphic parents
            DeclaredType directSuper = (DeclaredType)targetClassElt.getSuperclass();
            TypeElement superElt = (TypeElement)directSuper.asElement();
            // Anonymous subtypes of polymorphic classes other than object can't inherit
            if (getDeclAnnotation(superElt, PolyUIType.class) != null &&
                    !TypesUtils.isObject(directSuper)) {
                canInheritParentEffects = false;
            } else {
                for (TypeMirror ifaceM : targetClassElt.getInterfaces()) {
                    DeclaredType iface = (DeclaredType)ifaceM;
                    TypeElement ifaceElt = (TypeElement)iface.asElement();
                    if (getDeclAnnotation(ifaceElt, PolyUIType.class) != null) {
                        canInheritParentEffects = false;
                    }
                }
            }

            if (canInheritParentEffects) {
                Effect.EffectRange r = findInheritedEffectRange(targetClassElt, methodElt);
                return (r != null ? Effect.min(r.min, r.max) : new Effect(SafeEffect.class));
            }
        }

        return new Effect(SafeEffect.class);
    }

    // Only the visitMethod call should pass true for warnings
    public Effect.EffectRange findInheritedEffectRange(
            TypeElement declaringType, ExecutableElement overridingMethod) {
        return findInheritedEffectRange(declaringType, overridingMethod, false, null);
    }

    public Effect.EffectRange findInheritedEffectRange(
            TypeElement declaringType, ExecutableElement overridingMethod,
            boolean issueConflictWarning, Tree errorNode) {
      assert (declaringType != null);
        ExecutableElement ui_override = null;
        ExecutableElement safe_override = null;
        ExecutableElement poly_override = null;

        // We must account for explicit annotation, type declaration annotations, and package annotations
        boolean isUI = (getDeclAnnotation(overridingMethod, UIEffect.class) != null || isUIType(declaringType))
                      && getDeclAnnotation(overridingMethod, SafeEffect.class) == null;
        boolean isPolyUI = getDeclAnnotation(overridingMethod, PolyUIEffect.class) != null;

        // TODO: We must account for @UI and @AlwaysSafe annotations for extends
        // and implements clauses, and do the proper substitution of @Poly effects and quals!
        // List interfaces = declaringType.getInterfaces();
        TypeMirror superclass = declaringType.getSuperclass();
        while (superclass != null && superclass.getKind() != TypeKind.NONE) {
            ExecutableElement overrides = findJavaOverride(overridingMethod, superclass);
            if (overrides != null) {
                Effect eff = getDeclaredEffect(overrides);
                assert (eff != null);
                if (eff.isSafe()) {
                    // found a safe override
                    safe_override = overrides;
                    if (isUI && issueConflictWarning) {
                        checker.report(Result.failure("override.effect.invalid", overridingMethod, declaringType, safe_override, superclass), errorNode);
                    }
                    if (isPolyUI && issueConflictWarning) {
                        checker.report(Result.failure("override.effect.invalid.polymorphic", overridingMethod, declaringType, safe_override, superclass), errorNode);
                    }
                } else if (eff.isUI()) {
                    // found a ui override
                    ui_override = overrides;
                } else {
                    assert (eff.isPoly());
                    poly_override = overrides;
                    // TODO: Is this right? is the supertype covered by the
                    // directSuperTypes() method all I need? Or should I be
                    // using that utility method that returns a set of
                    // annodecl-method pairs given a method that overrides stuff
                    // if (isUI && issueConflictWarning) {
                    //    AnnotatedTypeMirror.AnnotatedDeclaredType supdecl = fromElement((TypeElement)(((DeclaredType)superclass).asElement()));//((DeclaredType)superclass).asElement());
                    //    // Need to special case an anonymous class with @UI on the decl, because "new @UI Runnable {...}" parses as @UI on an anon class decl extending Runnable
                    //    boolean isAnonInstantiation = TypesUtils.isAnonymousType(ElementUtils.getType(declaringType)) && getDeclAnnotation(declaringType, UI.class) != null;
                    //    if (!isAnonInstantiation && !hasAnnotationByName(supdecl, UI.class)) {
                    //        checker.report(Result.failure("override.effect.invalid", "non-UI instantiation of "+supdecl), errorNode);
                    //        If uncommenting this, change the above line to match other calls of Result.failure("override.effect.invalid", ...)
                    //    }
                    //}
                }
            }
            DeclaredType decl = (DeclaredType)superclass;
            superclass = ((TypeElement)decl.asElement()).getSuperclass();
        }

        AnnotatedTypeMirror.AnnotatedDeclaredType annoDecl = fromElement(declaringType);
        for (AnnotatedTypeMirror.AnnotatedDeclaredType ty : annoDecl.directSuperTypes()) {
            ExecutableElement overrides = findJavaOverride(overridingMethod, ty.getUnderlyingType());
            if (overrides != null) {
                Effect eff = getDeclaredEffect(overrides);
                if (eff.isSafe()) {
                    // found a safe override
                    safe_override = overrides;
                    if (isUI && issueConflictWarning) {
                        checker.report(Result.failure("override.effect.invalid", overridingMethod, declaringType, safe_override, ty), errorNode);
                    }
                    if (isPolyUI && issueConflictWarning) {
                        checker.report(Result.failure("override.effect.invalid.polymorphic", overridingMethod, declaringType, safe_override, ty), errorNode);
                    }
                } else if (eff.isUI()) {
                    // found a ui override
                    ui_override = overrides;
                } else {
                    assert (eff.isPoly());
                    poly_override = overrides;
                    if (isUI && issueConflictWarning) {
                        AnnotatedTypeMirror.AnnotatedDeclaredType supdecl = ty;
                        // Need to special case an anonymous class with @UI on
                        // the decl, because "new @UI Runnable {...}" parses as
                        // @UI on an anon class decl extending Runnable
                        boolean isAnonInstantiation = isAnonymousType(declaringType) &&
                                fromElement(declaringType).hasAnnotation(UI.class);
                        if (!isAnonInstantiation && !supdecl.hasAnnotation(UI.class)) {
                            checker.report(Result.failure("override.effect.invalid.nonui", overridingMethod, declaringType, poly_override, supdecl), errorNode);
                        }
                    }
                }
            }
        }

        // We don't need to issue warnings for inheriting from poly and a concrete effect.
        if (ui_override != null && safe_override != null && issueConflictWarning) {
            // There may be more than two parent methods, but for now it's
            // enough to know there are at least 2 in conflict
            checker.report(
                    Result.warning("override.effect.warning.inheritance",
                            overridingMethod,
                            declaringType,
                            ui_override.toString(),
                            ui_override.getEnclosingElement().asType().toString(),
                            safe_override.toString(),
                            safe_override.getEnclosingElement().asType().toString()),
                            errorNode);
        }

        Effect min = (safe_override != null ? new Effect(SafeEffect.class) :
                        (poly_override != null ? new Effect(PolyUIEffect.class) :
                           (ui_override != null ? new Effect(UIEffect.class) : null)));
        Effect max = (ui_override != null ? new Effect(UIEffect.class) :
                        (poly_override != null ? new Effect(PolyUIEffect.class) :
                           (safe_override != null ? new Effect(SafeEffect.class) : null)));
        if (debugSpew) {
            System.err.println("Found " + declaringType + "." + overridingMethod +
                    " to have inheritance pair (" + min + "," + max + ")");
        }

        if (min == null && max == null) {
            return null;
        } else {
            return new Effect.EffectRange(min, max);
        }
    }

    @Override
    protected TreeAnnotator createTreeAnnotator() {
        return new ListTreeAnnotator(
                super.createTreeAnnotator(),
                new GuiEffectTreeAnnotator()
        );
    }

    /**
     * A class for adding annotations based on tree.
     */
    private class GuiEffectTreeAnnotator extends TreeAnnotator {

        GuiEffectTreeAnnotator() {
            super(GuiEffectTypeFactory.this);
        }

        public boolean hasExplicitUIEffect(ExecutableElement methElt) {
            return GuiEffectTypeFactory.this.getDeclAnnotation(methElt, UIEffect.class) != null;
        }

        public boolean hasExplicitSafeEffect(ExecutableElement methElt) {
            return GuiEffectTypeFactory.this.getDeclAnnotation(methElt, SafeEffect.class) != null;
        }

        public boolean hasExplicitPolyUIEffect(ExecutableElement methElt) {
            return GuiEffectTypeFactory.this.getDeclAnnotation(methElt, PolyUIEffect.class) != null;
        }

        public boolean hasExplicitEffect(ExecutableElement methElt) {
            return hasExplicitUIEffect(methElt) ||
                    hasExplicitSafeEffect(methElt) ||
                    hasExplicitPolyUIEffect(methElt);
        }

        @Override
        public Void visitMethod(MethodTree node, AnnotatedTypeMirror type) {
            AnnotatedTypeMirror.AnnotatedExecutableType methType = (AnnotatedTypeMirror.AnnotatedExecutableType)type;
            Effect e = getDeclaredEffect(methType.getElement());
            TypeElement cls = (TypeElement)methType.getElement().getEnclosingElement();

            // STEP 1: Get the method effect annotation
            if (!hasExplicitEffect(methType.getElement())) {
                // TODO: This line does nothing!
                // AnnotatedTypeMirror.addAnnotation silently ignores non-qualifier annotations!
                // We should be digging up the /declaration/ of the method, and annotating that.
                methType.addAnnotation(e.getAnnot());
            }

            // STEP 2: Fix up the method receiver annotation
            AnnotatedTypeMirror.AnnotatedDeclaredType receiverType = methType.getReceiverType();
            if (receiverType != null &&
                    !receiverType.isAnnotatedInHierarchy(AnnotationUtils.fromClass(elements, UI.class))) {
                receiverType.addAnnotation(isPolymorphicType(cls) ? PolyUI.class :
                    fromElement(cls).hasAnnotation(UI.class) ? UI.class : AlwaysSafe.class );
            }
            return super.visitMethod(node, type);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy