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

checker.src.org.checkerframework.checker.formatter.FormatterTreeUtil 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.formatter;

/*>>>
import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
*/

import org.checkerframework.checker.formatter.qual.ConversionCategory;
import org.checkerframework.checker.formatter.qual.Format;
import org.checkerframework.checker.formatter.qual.FormatMethod;
import org.checkerframework.checker.formatter.qual.InvalidFormat;
import org.checkerframework.checker.formatter.qual.ReturnsFormat;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.dataflow.cfg.node.ArrayCreationNode;
import org.checkerframework.dataflow.cfg.node.FieldAccessNode;
import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
import org.checkerframework.dataflow.cfg.node.Node;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.util.AnnotationBuilder;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.TreeUtils;

import java.util.IllegalFormatException;
import java.util.List;
import java.util.Locale;

import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.NullType;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.SimpleElementVisitor7;
import javax.lang.model.util.SimpleTypeVisitor7;

import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.TypeCastTree;
import com.sun.source.util.SimpleTreeVisitor;

/**
 * This class provides a collection of utilities to ease working
 * with syntax trees that have something to do with Formatters.
 *
 * @author Konstantin Weitz
 */
public class FormatterTreeUtil {
    public final BaseTypeChecker checker;
    public final ProcessingEnvironment processingEnv;
    private final ExecutableElement formatArgTypesElement;

    public FormatterTreeUtil(BaseTypeChecker checker) {
        this.checker = checker;
        this.processingEnv = checker.getProcessingEnvironment();
        this.formatArgTypesElement = TreeUtils.getMethod(
                org.checkerframework.checker.formatter.qual.Format.class.getCanonicalName(),
                "value", 0, processingEnv);
    }

    /**
     * Describes the ways a format method may be invoked.
     */
    public enum InvocationType {
        /**
         * The parameters are passed as varargs. For example:
         *
         * 
*
         * String.format("%s %d", "Example", 7);
         * 
*
*/ VARARG, /** * The parameters are passed as array. For example: * *
*
         * Object[] a = new Object[]{"Example",7};
         * String.format("%s %d", a);
         * 
*
*/ ARRAY, /** * A null array is passed to the format method. * This happens seldomly. * *
*
         * String.format("%s %d", (Object[])null);
         * 
*
*/ NULLARRAY; } public interface Result { E value(); } private static class ResultImpl implements Result { private final E value; public final ExpressionTree location; public ResultImpl(E value, ExpressionTree location) { this.value = value; this.location = location; } @Override public E value() { return value; } } public boolean isAsFormatCall(MethodInvocationNode node, AnnotatedTypeFactory atypeFactory) { ExecutableElement method = node.getTarget().getMethod(); AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, ReturnsFormat.class); return anno != null; } private ConversionCategory[] asFormatCallCategoriesLowLevel(MethodInvocationNode node) { Node vararg = node.getArgument(1); if (vararg instanceof ArrayCreationNode) { List convs = ((ArrayCreationNode)vararg).getInitializers(); ConversionCategory[] res = new ConversionCategory[convs.size()]; for (int i = 0; i < convs.size(); ++i) { Node conv = convs.get(i); if (conv instanceof FieldAccessNode) { Class clazz = typeMirrorToClass(((FieldAccessNode) conv).getType()); if (clazz == ConversionCategory.class) { res[i] = ConversionCategory.valueOf(((FieldAccessNode) conv).getFieldName()); continue; /* avoid returning null */ } } return null; } return res; } return null; } public Result asFormatCallCategories(MethodInvocationNode node) { // TODO make sure the method signature looks good return new ResultImpl(asFormatCallCategoriesLowLevel(node), node.getTree()); } public boolean isFormatCall(MethodInvocationTree node, AnnotatedTypeFactory atypeFactory) { ExecutableElement method = TreeUtils.elementFromUse(node); AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, FormatMethod.class); return anno != null; } /** * Represents a format method invocation in the syntax tree. */ public class FormatCall { private final AnnotatedTypeMirror formatAnno; private final List args; private final MethodInvocationTree node; private final ExpressionTree formatArg; private final AnnotatedTypeFactory atypeFactory; public FormatCall(MethodInvocationTree node, AnnotatedTypeFactory atypeFactory) { this.node = node; // TODO figure out how to make passing of environment // objects such as atypeFactory, processingEnv, ... nicer this.atypeFactory = atypeFactory; List theargs; theargs = node.getArguments(); if (typeMirrorToClass(atypeFactory.getAnnotatedType(theargs.get(0)).getUnderlyingType()) == Locale.class) { // call with Locale as first argument theargs = theargs.subList(1, theargs.size()); } // TODO check that the first parameter exists and is a string formatArg = theargs.get(0); formatAnno = atypeFactory.getAnnotatedType(formatArg); this.args = theargs.subList(1, theargs.size()); } /** * Returns an error description if the format string cannot be satisfied. * Returns null if the format string does not contain syntactic errors. */ public final Result isIllegalFormat() { String res = null; if (!formatAnno.hasAnnotation(Format.class)) { res = "(is a @Format annotation missing?)"; AnnotationMirror inv = formatAnno.getAnnotation(InvalidFormat.class); if (inv != null) { res = invalidFormatAnnotationToErrorMessage(inv); } } return new ResultImpl(res, formatArg); } /** * Returns the type of method invocation. * * @see InvocationType */ public final Result getInvocationType() { InvocationType type = InvocationType.VARARG; if (args.size() == 1) { final ExpressionTree first = args.get(0); TypeMirror argType = atypeFactory.getAnnotatedType(first).getUnderlyingType(); // figure out if argType is an array type = argType.accept( new SimpleTypeVisitor7>() { @Override protected InvocationType defaultAction(TypeMirror e, Class p) { // not an array return InvocationType.VARARG; } @Override public InvocationType visitArray(ArrayType t, Class p) { // it's an array, now figure out if it's a (Object[])null array return first.accept(new SimpleTreeVisitor>() { @Override protected InvocationType defaultAction( Tree node, Class p) { // just a normal array return InvocationType.ARRAY; } @Override public InvocationType visitTypeCast( TypeCastTree node, Class p) { // it's a (Object[])null return atypeFactory.getAnnotatedType( node.getExpression()).getUnderlyingType(). getKind() == TypeKind.NULL ? InvocationType.NULLARRAY : InvocationType.ARRAY; } }, p); } @Override public InvocationType visitNull(NullType t, Class p) { return InvocationType.NULLARRAY; } }, Void.TYPE); } ExpressionTree loc = node.getMethodSelect(); if (type!=InvocationType.VARARG && args.size() > 0) { loc = args.get(0); } return new ResultImpl(type, loc); } /** * Returns the conversion category for every parameter. * * @see ConversionCategory */ public final ConversionCategory[] getFormatCategories() { AnnotationMirror anno = formatAnno.getAnnotation(Format.class); return formatAnnotationToCategories(anno); } /** * Returns the type of the function's parameters. * Use {@link #isValidParameter(ConversionCategory, TypeMirror) isValidParameter} * and {@link #isParameterNull(TypeMirror) isParameterNull} * to work with the result. */ public final Result[] getParamTypes() { // One to make javac happy, the other to make Eclipse happy... @SuppressWarnings({"rawtypes", "unchecked"}) Result[] res = new Result[args.size()]; for (int i = 0; i < res.length; ++i) { ExpressionTree arg = args.get(i); TypeMirror argType = atypeFactory.getAnnotatedType(arg).getUnderlyingType(); res[i] = new ResultImpl(argType, arg); } return res; } /** * Checks if the type of a parameter returned from {@link #getParamTypes()} * is valid for the passed ConversionCategory. */ public final boolean isValidParameter(ConversionCategory formatCat, TypeMirror paramType) { Class type = typeMirrorToClass(paramType); if (type == null) { // we did not recognize the parameter type return false; } for (Class c : formatCat.types) { if (c.isAssignableFrom(type)) { return true; } } return false; } /** * Checks if the parameter returned from {@link #getParamTypes()} is a * {@code null} expression. */ public final boolean isParameterNull(TypeMirror type) { // is it the null literal return type.accept( new SimpleTypeVisitor7>() { @Override protected Boolean defaultAction(TypeMirror e, Class p) { // it's not the null literal return false; } @Override public Boolean visitNull(NullType t, Class p) { // it's the null literal return true; } }, Void.TYPE); } } /** * Reports an error. Takes a {@link Result} to report the location. */ public final void failure(Result res, /*@CompilerMessageKey*/ String msg, Object... args) { ResultImpl impl = (ResultImpl)res; checker.report(org.checkerframework.framework.source.Result.failure(msg, args), impl.location); } /** * Reports an warning. Takes a {@link Result} to report the location. */ public final void warning(Result res, /*@CompilerMessageKey*/ String msg, Object... args) { ResultImpl impl = (ResultImpl)res; checker.report(org.checkerframework.framework.source.Result.warning(msg, args), impl.location); } /** * Takes an exception that describes an invalid formatter string and, returns a syntax trees * element that represents a {@link InvalidFormat} annotation with the exception's error * message as value. */ public AnnotationMirror exceptionToInvalidFormatAnnotation(IllegalFormatException ex) { AnnotationBuilder builder = new AnnotationBuilder(processingEnv, InvalidFormat.class.getCanonicalName()); builder.setValue("value", ex.getMessage()); return builder.build(); } /** * Takes a syntax tree element that represents a {@link InvalidFormat} annotation, * and returns its value. */ public String invalidFormatAnnotationToErrorMessage(AnnotationMirror anno) { return "\""+AnnotationUtils.getElementValue(anno, "value", String.class, true)+"\""; } /** * Takes a list of ConversionCategory elements, and returns a syntax tree * element that represents a {@link Format} annotation with the list as * value. */ public AnnotationMirror categoriesToFormatAnnotation(ConversionCategory[] args) { AnnotationBuilder builder = new AnnotationBuilder(processingEnv, Format.class.getCanonicalName()); builder.setValue("value", args); return builder.build(); } /** * Takes a syntax tree element that represents a {@link Format} annotation, * and returns its value. */ public ConversionCategory[] formatAnnotationToCategories(AnnotationMirror anno) { List list = AnnotationUtils.getElementValueEnumArray(anno, "value", ConversionCategory.class, false); return list.toArray(new ConversionCategory[] {}); } private final Class typeMirrorToClass(final TypeMirror type) { return type.accept( new SimpleTypeVisitor7, Class>() { @Override public Class visitPrimitive( PrimitiveType t, Class v) { switch (t.getKind()) { case BOOLEAN: return Boolean.class; case BYTE: return Byte.class; case CHAR: return Character.class; case SHORT: return Short.class; case INT: return Integer.class; case LONG: return Long.class; case FLOAT: return Float.class; case DOUBLE: return Double.class; default: return null; } } @Override public Class visitDeclared( DeclaredType dt, Class v) { return dt.asElement().accept(new SimpleElementVisitor7, Class>() { @Override public Class visitType(TypeElement e, Class v) { try { return Class.forName(e.getQualifiedName().toString()); } catch (ClassNotFoundException e1) { return null; // the lookup should work for all the classes we care about } } }, Void.TYPE); } }, Void.TYPE); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy