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

org.checkerframework.checker.formatter.FormatterVisitor 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.

The newest version!
package org.checkerframework.checker.formatter;

import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;

import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
import org.checkerframework.checker.formatter.FormatterTreeUtil.FormatCall;
import org.checkerframework.checker.formatter.FormatterTreeUtil.InvocationType;
import org.checkerframework.checker.formatter.FormatterTreeUtil.Result;
import org.checkerframework.checker.formatter.qual.ConversionCategory;
import org.checkerframework.checker.formatter.qual.FormatMethod;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.common.basetype.BaseTypeVisitor;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.TreePathUtil;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;
import org.checkerframework.javacutil.UserError;

import java.util.List;

import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;

/* NO-AFU
   import org.checkerframework.common.wholeprograminference.WholeProgramInference;
*/

/**
 * Whenever a format method invocation is found in the syntax tree, checks are performed as
 * specified in the Format String Checker manual.
 *
 * @checker_framework.manual #formatter-guarantees Format String Checker
 */
public class FormatterVisitor extends BaseTypeVisitor {
    public FormatterVisitor(BaseTypeChecker checker) {
        super(checker);
    }

    @Override
    public Void visitMethod(MethodTree tree, Void p) {
        ExecutableElement methodElement = TreeUtils.elementFromDeclaration(tree);
        if (atypeFactory.getDeclAnnotation(methodElement, FormatMethod.class) != null) {
            int formatStringIndex = FormatterVisitor.formatStringIndex(methodElement);
            if (formatStringIndex == -1) {
                checker.reportError(tree, "format.method.invalid", methodElement.getSimpleName());
            }
        }
        return super.visitMethod(tree, p);
    }

    @Override
    public Void visitMethodInvocation(MethodInvocationTree tree, Void p) {
        FormatterTreeUtil ftu = atypeFactory.treeUtil;
        FormatCall fc = ftu.create(tree, atypeFactory);
        if (fc != null) {
            MethodTree enclosingMethod =
                    TreePathUtil.enclosingMethod(atypeFactory.getPath(fc.invocationTree));

            Result errMissingFormat = fc.errMissingFormatAnnotation();
            if (errMissingFormat != null) {
                // The string's type has no @Format annotation.
                if (isWrappedFormatCall(fc, enclosingMethod)) {
                    // Nothing to do, because call is legal.
                } else {
                    // I.1
                    ftu.failure(
                            errMissingFormat, "format.string.invalid", errMissingFormat.value());
                }
            } else {
                // The string has a @Format annotation.
                Result invc = fc.getInvocationType();
                ConversionCategory[] formatCats = fc.getFormatCategories();
                switch (invc.value()) {
                    case VARARG:
                        Result[] argTypes = fc.getArgTypes();
                        int argl = argTypes.length;
                        int formatl = formatCats.length;
                        if (argl < formatl) {
                            // For assignments, format.missing.arguments is issued from
                            // commonAssignmentCheck().
                            // II.1
                            ftu.failure(invc, "format.missing.arguments", formatl, argl);
                        } else {
                            if (argl > formatl) {
                                // II.2
                                ftu.warning(invc, "format.excess.arguments", formatl, argl);
                            }
                            for (int i = 0; i < formatl; ++i) {
                                ConversionCategory formatCat = formatCats[i];
                                Result arg = argTypes[i];
                                TypeMirror argType = arg.value();

                                switch (formatCat) {
                                    case UNUSED:
                                        // I.2
                                        ftu.warning(arg, "format.argument.unused", " " + (1 + i));
                                        break;
                                    case NULL:
                                        // I.3
                                        if (argType.getKind() == TypeKind.NULL) {
                                            ftu.warning(
                                                    arg, "format.specifier.null", " " + (1 + i));
                                        } else {
                                            ftu.failure(
                                                    arg, "format.specifier.null", " " + (1 + i));
                                        }
                                        break;
                                    case GENERAL:
                                        break;
                                    default:
                                        if (!fc.isValidArgument(formatCat, argType)) {
                                            // II.3
                                            ExecutableElement method =
                                                    TreeUtils.elementFromUse(tree);
                                            CharSequence methodName =
                                                    ElementUtils.getSimpleDescription(method);
                                            ftu.failure(
                                                    arg,
                                                    "argument.type.incompatible",
                                                    "in varargs position",
                                                    methodName,
                                                    argType,
                                                    formatCat);
                                        }
                                        break;
                                }
                            }
                        }
                        break;
                    case ARRAY:
                        // III
                        if (!isWrappedFormatCall(fc, enclosingMethod)) {
                            ftu.warning(invc, "format.indirect.arguments");
                        }
                        // TODO:  If it is explict array construction, such as "new Object[] {
                        // ... }", then we could treat it like the VARARGS case, analyzing each
                        // argument.  "new array" is probably rare, in the varargs position.
                        // fall through
                    case NULLARRAY:
                        for (ConversionCategory cat : formatCats) {
                            if (cat == ConversionCategory.NULL) {
                                // I.3
                                if (invc.value() == FormatterTreeUtil.InvocationType.NULLARRAY) {
                                    ftu.warning(invc, "format.specifier.null", "");
                                } else {
                                    ftu.failure(invc, "format.specifier.null", "");
                                }
                            }
                            if (cat == ConversionCategory.UNUSED) {
                                // I.2
                                ftu.warning(invc, "format.argument.unused", "");
                            }
                        }
                        break;
                }
            }

            /* NO-AFU
                   // Support -Ainfer command-line argument.
                   WholeProgramInference wpi = atypeFactory.getWholeProgramInference();
                   if (wpi != null && forwardsArguments(tree, enclosingMethod)) {
                       wpi.addMethodDeclarationAnnotation(
                               TreeUtils.elementFromDeclaration(enclosingMethod),
                               atypeFactory.FORMATMETHOD);
                   }
            */
        }
        return super.visitMethodInvocation(tree, p);
    }

    /**
     * Returns true if {@code fc} is within a method m annotated as {@code @FormatMethod}, and fc's
     * arguments are m's formal parameters. In other words, fc forwards m's arguments to another
     * format method.
     *
     * @param fc an invocation of a format method
     * @param enclosingMethod the method that contains the call
     * @return true if {@code fc} is a call to a format method that forwards its containing method's
     *     arguments
     */
    private boolean isWrappedFormatCall(FormatCall fc, @Nullable MethodTree enclosingMethod) {
        if (enclosingMethod == null) {
            return false;
        }
        ExecutableElement enclosingMethodElement =
                TreeUtils.elementFromDeclaration(enclosingMethod);
        boolean withinFormatMethod =
                (atypeFactory.getDeclAnnotation(enclosingMethodElement, FormatMethod.class)
                        != null);
        return withinFormatMethod && forwardsArguments(fc.invocationTree, enclosingMethod);
    }

    /**
     * Returns true if {@code invocationTree}'s arguments are {@code enclosingMethod}'s formal
     * parameters. In other words, {@code invocationTree} forwards {@code enclosingMethod}'s
     * arguments.
     *
     * 

Only arguments from the first String formal parameter onward count. Returns false if there * is no String formal parameter. * * @param invocationTree an invocation of a method * @param enclosingMethod the method that contains the call * @return true if {@code invocationTree} is a call to a method that forwards its containing * method's arguments */ private boolean forwardsArguments( MethodInvocationTree invocationTree, @Nullable MethodTree enclosingMethod) { if (enclosingMethod == null) { return false; } ExecutableElement enclosingMethodElement = TreeUtils.elementFromDeclaration(enclosingMethod); int paramIndex = formatStringIndex(enclosingMethodElement); if (paramIndex == -1) { return false; } ExecutableElement calledMethodElement = TreeUtils.elementFromUse(invocationTree); int callIndex = formatStringIndex(calledMethodElement); if (callIndex == -1) { throw new UserError( "Method " + calledMethodElement + " is annotated @FormatMethod but has no String formal parameter"); } List args = invocationTree.getArguments(); List params = enclosingMethod.getParameters(); if (params.size() - paramIndex != args.size() - callIndex) { return false; } while (paramIndex < params.size()) { ExpressionTree argTree = args.get(callIndex); if (argTree.getKind() != Tree.Kind.IDENTIFIER) { return false; } VariableTree param = params.get(paramIndex); if (param.getName() != ((IdentifierTree) argTree).getName()) { return false; } paramIndex++; callIndex++; } return true; } // TODO: Should this be the last String argument? That would require that every method // annotated with @FormatMethod uses varargs syntax. /** * Returns the index of the format string of a method: the first formal parameter with declared * type String. * * @param m a method * @return the index of the last String formal parameter, or -1 if none */ public static int formatStringIndex(ExecutableElement m) { List params = m.getParameters(); for (int i = 0; i < params.size(); i++) { if (TypesUtils.isString(params.get(i).asType())) { return i; } } return -1; } @Override protected boolean commonAssignmentCheck( AnnotatedTypeMirror varType, AnnotatedTypeMirror valueType, Tree valueTree, @CompilerMessageKey String errorKey, Object... extraArgs) { boolean result = super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs); AnnotationMirror rhs = valueType.getAnnotationInHierarchy(atypeFactory.UNKNOWNFORMAT); AnnotationMirror lhs = varType.getAnnotationInHierarchy(atypeFactory.UNKNOWNFORMAT); // From the manual: "It is legal to use a format string with fewer format specifiers // than required, but a warning is issued." // The format.missing.arguments warning is issued here for assignments. // For method calls, it is issued in visitMethodInvocation. if (rhs != null && lhs != null && AnnotationUtils.areSameByName(rhs, FormatterAnnotatedTypeFactory.FORMAT_NAME) && AnnotationUtils.areSameByName(lhs, FormatterAnnotatedTypeFactory.FORMAT_NAME)) { ConversionCategory[] rhsArgTypes = atypeFactory.treeUtil.formatAnnotationToCategories(rhs); ConversionCategory[] lhsArgTypes = atypeFactory.treeUtil.formatAnnotationToCategories(lhs); if (rhsArgTypes.length < lhsArgTypes.length) { checker.reportWarning( valueTree, "format.missing.arguments", varType.toString(), valueType.toString()); result = false; } } return result; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy