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

framework.src.org.checkerframework.common.reflection.MethodValAnnotatedTypeFactory 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.common.reflection;

import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.common.reflection.qual.ClassBound;
import org.checkerframework.common.reflection.qual.ClassVal;
import org.checkerframework.common.reflection.qual.GetConstructor;
import org.checkerframework.common.reflection.qual.GetMethod;
import org.checkerframework.common.reflection.qual.MethodVal;
import org.checkerframework.common.reflection.qual.MethodValBottom;
import org.checkerframework.common.reflection.qual.UnknownMethod;
import org.checkerframework.common.value.ValueAnnotatedTypeFactory;
import org.checkerframework.common.value.ValueChecker;
import org.checkerframework.common.value.qual.ArrayLen;
import org.checkerframework.common.value.qual.BottomVal;
import org.checkerframework.common.value.qual.StringVal;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.QualifierHierarchy;
import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
import org.checkerframework.framework.util.AnnotationBuilder;
import org.checkerframework.framework.util.MultiGraphQualifierHierarchy;
import org.checkerframework.framework.util.MultiGraphQualifierHierarchy.MultiGraphFactory;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.InternalUtils;
import org.checkerframework.javacutil.TreeUtils;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.lang.model.element.AnnotationMirror;

import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;

public class MethodValAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
    private final AnnotationMirror METHODVAL_BOTTOM = AnnotationUtils
            .fromClass(elements, MethodValBottom.class);
    private final AnnotationMirror UNKNOWN_METHOD = AnnotationUtils.fromClass(
            elements, UnknownMethod.class);

    private static final int UNKNOWN_PARAM_LENGTH = -1;

    public MethodValAnnotatedTypeFactory(BaseTypeChecker checker) {
        super(checker);
        if (this.getClass().equals(MethodValAnnotatedTypeFactory.class)) {
            this.postInit();
        }
    }

    @Override
    protected Set> createSupportedTypeQualifiers() {
        return Collections.unmodifiableSet(
                new HashSet>(
                        Arrays.asList(MethodVal.class, MethodValBottom.class, UnknownMethod.class)));
    }

    @Override
    protected void initilizeReflectionResolution() {
        boolean debug = "debug".equals(checker.getOption("resolveReflection"));
        reflectionResolver = new DefaultReflectionResolver(checker, this,
                debug);
    }

    static List getListOfMethodSignatures(AnnotationMirror anno) {
        List list = new ArrayList<>();
        List methodNames = AnnotationUtils.getElementValueArray(anno,
                "methodName", String.class, true);
        List classNames = AnnotationUtils.getElementValueArray(anno,
                "className", String.class, true);
        List params = AnnotationUtils.getElementValueArray(anno,
                "params", Integer.class, true);
        for (int i = 0; i < methodNames.size(); i++) {
            list.add(new MethodSignature(classNames.get(i),
                    methodNames.get(i),
                    params.get(i)));
        }
        return list;
    }

    private AnnotationMirror createMethodVal(Set sigs) {
        List classNames = new ArrayList<>();
        List methodNames = new ArrayList<>();
        List params = new ArrayList<>();
        for (MethodSignature sig:sigs) {
            classNames.add(sig.className);
            methodNames.add(sig.methodName);
            params.add(sig.params);
        }
        AnnotationBuilder builder = new AnnotationBuilder(processingEnv,
                MethodVal.class.getCanonicalName());
        builder.setValue("className", classNames);
        builder.setValue("methodName", methodNames);
        builder.setValue("params", params);
        return builder.build();
    }
    /**
     * Returns a list of class names for the given tree using the Class Val Checker
     * @param tree ExpressionTree whose class names are requested
     * @param mustBeExact whether @ClassBound may be used
     * @return list of class names or the empty list if no class names were found
     */
    private List getClassNamesFromClassValChecker(ExpressionTree tree, boolean mustBeExact) {
        ClassValAnnotatedTypeFactory classValATF = getTypeFactoryOfSubchecker(ClassValChecker.class);
        AnnotatedTypeMirror classAnno = classValATF.getAnnotatedType(tree);
        List classNames = new ArrayList<>();
        AnnotationMirror annotation = classAnno.getAnnotation(ClassVal.class);
        if (annotation != null) {
            classNames = AnnotationUtils.getElementValueArray(annotation,
                    "value", String.class, true);
        } else if (!mustBeExact) {
            // Could be ClassBound instead of ClassVal
            annotation = classAnno.getAnnotation(ClassBound.class);
            if (annotation != null) {
                classNames = AnnotationUtils.getElementValueArray(annotation,
                        "value", String.class, true);
            }
        }
        return classNames;
    }
    /**
     * Returns the string values for the argument passed.  The String Values
     * are estimated using the Value Checker.
     * @param arg ExpressionTree whose string values are sought
     * @return string values of arg or the empty list if no values were found
     */
    private List getMethodNamesFromStringArg(ExpressionTree arg) {
        List methodNames = new ArrayList<>();
        ValueAnnotatedTypeFactory valueATF = getTypeFactoryOfSubchecker(ValueChecker.class);
        AnnotatedTypeMirror valueAnno = valueATF.getAnnotatedType(arg);
        AnnotationMirror annotation = valueAnno.getAnnotation(StringVal.class);
        if (annotation != null) {
            methodNames = AnnotationUtils.getElementValueArray(annotation,
                    "value", String.class, true);
        }
        return methodNames;
    }

    @Override
    public QualifierHierarchy createQualifierHierarchy(MultiGraphFactory factory) {
        return new MethodValQualifierHierarchy(factory, METHODVAL_BOTTOM);
    }

    protected class MethodValQualifierHierarchy extends
            MultiGraphQualifierHierarchy {

        protected MethodValQualifierHierarchy(
                MultiGraphQualifierHierarchy.MultiGraphFactory factory,
                AnnotationMirror bottom) {
            super(factory, bottom);
        }

        /*
         * Determines the least upper bound of a1 and a2. If both are MethodVal
         * annotations, then the least upper bound is the result of
         * concatenating all value lists of a1 and a2.
         */
        @Override
        public AnnotationMirror leastUpperBound(AnnotationMirror a1,
                AnnotationMirror a2) {
            if (!AnnotationUtils.areSameIgnoringValues(getTopAnnotation(a1),
                    getTopAnnotation(a2))) {
                return null;
            } else if (isSubtype(a1, a2)) {
                return a2;
            } else if (isSubtype(a2, a1)) {
                return a1;
            } else if (AnnotationUtils.areSameIgnoringValues(a1, a2)) {
                List a1Sigs = getListOfMethodSignatures(a1);
                List a2Sigs = getListOfMethodSignatures(a2);

                Set lubSigs = new HashSet(a1Sigs);
                lubSigs.addAll(a2Sigs);

                AnnotationMirror result = createMethodVal(lubSigs);
                return result;
            }
            return null;
        }

        @Override
        public boolean isSubtype(AnnotationMirror sub, AnnotationMirror sup) {
            if (AnnotationUtils.areSame(sub, sup)
                    || AnnotationUtils.areSameByClass(sup, UnknownMethod.class)
                    || AnnotationUtils.areSameByClass(sub,
                            MethodValBottom.class)) {
                return true;
            }
            if (AnnotationUtils.areSameByClass(sub, UnknownMethod.class)
                    || AnnotationUtils.areSameByClass(sup, MethodValBottom.class)) {
                return false;
            }
            assert AnnotationUtils.areSameByClass(sub, MethodVal.class)
                    && AnnotationUtils.areSameByClass(sup, MethodVal.class) : "Unexpected annotation in MethodVal";
            List subSignatures = getListOfMethodSignatures(sub);
            List superSignatures = getListOfMethodSignatures(sup);
            for (MethodSignature sig : subSignatures) {
                if (!superSignatures.contains(sig)) {
                    return false;
                }
            }
            return true;
        }
    }

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

    /**
     * TreeAnnotator with the visitMethodInvocation method overridden
     */
    protected class MethodValTreeAnnotator extends TreeAnnotator {

        protected MethodValTreeAnnotator(MethodValAnnotatedTypeFactory factory) {
            super(factory);
        }

        /*
         * Special handling of getMethod and getDeclaredMethod calls. Attempts
         * to get the annotation on the Class object receiver to get the
         * className, the annotation on the String argument to get the
         * methodName, and the parameters argument to get the param, and then
         * builds a new MethodVal annotation from these
         */
        @Override
        public Void visitMethodInvocation(MethodInvocationTree tree,
                AnnotatedTypeMirror type) {

            List methodNames;
            List params;
            List classNames;
            if (isGetConstructorMethodInovaction(tree)) {
                // method name for constructors is always 
                methodNames = Arrays.asList(ReflectionResolver.INIT);
                params = getConstructorParamsLen(tree.getArguments());
                classNames = getClassNamesFromClassValChecker(TreeUtils.getReceiverTree(tree),
                        true);

            } else if (isGetMethodMethodInovaction(tree)) {
                ExpressionTree methodNameArg = tree.getArguments().get(0);
                methodNames = getMethodNamesFromStringArg(methodNameArg);
                params = getMethodParamsLen(tree.getArguments());
                classNames = getClassNamesFromClassValChecker(TreeUtils.getReceiverTree(tree),
                        false);
            } else {
                // Not a covered method invocation
                return null;
            }

            // Create MethodVal
            if (methodNames.isEmpty() || classNames.isEmpty()) {
                // No method name or classname is found, it could be any
                // class or method, so, return @UnknownMethod.
                type.replaceAnnotation(UNKNOWN_METHOD);
                return null;
            }

            Set methodSigs = new HashSet<>();

            // The possible method signatures are the Cartesian product of all
            // found class, method, and parameter lengths
            for (String methodName : methodNames) {
                for (String className : classNames) {
                    for (Integer param : params) {
                        methodSigs.add(new MethodSignature(className,
                                methodName, param));
                    }
                }
            }

            AnnotationMirror newQual = createMethodVal(methodSigs);
            type.replaceAnnotation(newQual);
            return null;
        }

        private boolean isGetConstructorMethodInovaction(MethodInvocationTree tree) {
            if (getDeclAnnotation(InternalUtils.symbol(tree),
                    GetConstructor.class) != null) {
                return true;
            }
            return false;
        }
        private boolean isGetMethodMethodInovaction(MethodInvocationTree tree) {
            if (getDeclAnnotation(InternalUtils.symbol(tree),
                    GetMethod.class) != null) {
                return true;
            }
           return false;
        }

        private List getMethodParamsLen(
                List args) {
            assert args.size() > 0 : "getMethod must have at least one parameter";

            // Number of parameters in the created method object
            int numParams = args.size() - 1;
            if (numParams == 1) {
                return getNumberOfParameterOneArg(args.get(1));
            }
            return Collections.singletonList(numParams);
        }

        private List getConstructorParamsLen(
                List args) {
            // Number of parameters in the created method object
            int numParams = args.size();
            if (numParams == 1) {
                return getNumberOfParameterOneArg(args.get(0));
            }
            return Collections.singletonList(numParams);
        }

        /**
         * if getMethod(Object receiver, Object... params) or
         * getConstrutor(Object... params) have one argument for params, then
         * the number of parameters in the underlying method or constructor must
         * be:
         *
         * 0: if the argument is null
         * x: if the argument is an array with @ArrayLen(x)
         * UNKNOWN_PARAM_LENGTH: if the argument is an array with @UnknownVal
         * 1: otherwise
         */
        private List getNumberOfParameterOneArg(ExpressionTree argument) {
            AnnotatedTypeMirror atm = atypeFactory.getAnnotatedType(argument);
            switch (atm.getKind()) {
            case ARRAY:
                ValueAnnotatedTypeFactory valueATF = getTypeFactoryOfSubchecker(ValueChecker.class);
                AnnotatedTypeMirror valueAnno = valueATF.getAnnotatedType(argument);
                if (valueAnno.getAnnotation(ArrayLen.class) != null) {
                    AnnotationMirror annotation = valueAnno
                            .getAnnotation(ArrayLen.class);
                    return AnnotationUtils.getElementValueArray(annotation, "value",
                            Integer.class, true);
                } else if (valueAnno.getAnnotation(BottomVal.class) != null) {
                    // happens in this case: (Class[]) null
                    return Collections.singletonList(0);
                }
                // the argument is an array with unknown array length
                return Collections.singletonList(UNKNOWN_PARAM_LENGTH);
            case NULL:
                // null is treated as the empty list of parameters, so size
                // is 0
                return Collections.singletonList(0);
            default:
                // The argument is not an array or null,
                // so it must be a class.
                return Collections.singletonList(1);
            }
        }


    }

}
/**
 * An object that represents a the tuple that identifies a method signature:
 * (fully qualified class name, method name, number of parameters)
 * @author smillst
 *
 */
class MethodSignature {
    String className;
    String methodName;
    int params;

    public MethodSignature(String className, String methodName, int params) {
        this.className = className;
        this.methodName = methodName;
        this.params = params;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result
                + ((className == null) ? 0 : className.hashCode());
        result = prime * result
                + ((methodName == null) ? 0 : methodName.hashCode());
        result = prime * result + params;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        MethodSignature other = (MethodSignature) obj;
        if (className == null) {
            if (other.className != null) {
                return false;
            }
        } else if (!className.equals(other.className)) {
            return false;
        }
        if (methodName == null) {
            if (other.methodName != null) {
                return false;
            }
        } else if (!methodName.equals(other.methodName)) {
            return false;
        }
        if (params != other.params) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "MethodSignature [className=" + className + ", methodName="
                + methodName + ", params=" + params + "]";
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy