package org.checkerframework.checker.i18nformatter; /*>>> import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey; */ import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.Tree; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.type.TypeMirror; import org.checkerframework.checker.formatter.FormatterTreeUtil.InvocationType; import org.checkerframework.checker.formatter.FormatterTreeUtil.Result; import org.checkerframework.checker.i18nformatter.I18nFormatterTreeUtil.FormatType; import org.checkerframework.checker.i18nformatter.I18nFormatterTreeUtil.I18nFormatCall; import org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory; import org.checkerframework.checker.i18nformatter.qual.I18nFormatFor; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.basetype.BaseTypeVisitor; import org.checkerframework.dataflow.cfg.node.MethodInvocationNode; import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.javacutil.AnnotationUtils; /** * Whenever a method with {@link I18nFormatFor} annotation is invoked, it will perform the format * string verification. * * @checker_framework.manual #i18n-formatter-checker Internationalization Format String Checker * @author Siwakorn Srisakaokul */ public class I18nFormatterVisitor extends BaseTypeVisitor<I18nFormatterAnnotatedTypeFactory> { public I18nFormatterVisitor(BaseTypeChecker checker) { super(checker); } @Override public Void visitMethodInvocation(MethodInvocationTree node, Void p) { MethodInvocationNode nodeNode = (MethodInvocationNode) atypeFactory.getNodeForTree(node); I18nFormatterTreeUtil tu = atypeFactory.treeUtil; I18nFormatCall fc = tu.createFormatForCall(node, nodeNode, atypeFactory); if (fc != null) { checkInvocationFormatFor(fc); return p; } return super.visitMethodInvocation(node, p); } private void checkInvocationFormatFor(I18nFormatCall fc) { I18nFormatterTreeUtil tu = atypeFactory.treeUtil; Result<FormatType> type = fc.getFormatType(); Result<InvocationType> invc; I18nConversionCategory[] formatCats; switch (type.value()) { case I18NINVALID: tu.failure(type, "i18nformat.string.invalid", fc.getInvalidError()); break; case I18NFORMATFOR: if (!fc.isValidFormatForInvocation()) { Result<FormatType> failureType = fc.getInvalidInvocationType(); tu.failure(failureType, "i18nformat.invalid.formatfor"); } break; case I18NFORMAT: invc = fc.getInvocationType(); formatCats = fc.getFormatCategories(); switch (invc.value()) { case VARARG: Result<TypeMirror>[] paramTypes = fc.getParamTypes(); int paraml = paramTypes.length; int formatl = formatCats.length; // For assignments, i18nformat.missing.arguments and // i18nformat.excess.arguments are issued // from commonAssignmentCheck. if (paraml < formatl) { tu.warning(invc, "i18nformat.missing.arguments", formatl, paraml); } if (paraml > formatl) { tu.warning(invc, "i18nformat.excess.arguments", formatl, paraml); } for (int i = 0; i < formatl && i < paraml; ++i) { I18nConversionCategory formatCat = formatCats[i]; Result<TypeMirror> param = paramTypes[i]; TypeMirror paramType = param.value(); switch (formatCat) { case UNUSED: tu.warning(param, "i18nformat.argument.unused", " " + (1 + i)); break; case GENERAL: break; default: if (!fc.isValidParameter(formatCat, paramType)) { tu.failure( param, "argument.type.incompatible", paramType, formatCat); } } } break; case NULLARRAY: // fall-through case ARRAY: for (I18nConversionCategory cat : formatCats) { if (cat == I18nConversionCategory.UNUSED) { tu.warning(invc, "i18nformat.argument.unused", ""); } } tu.warning(invc, "i18nformat.indirect.arguments"); break; default: break; } break; default: break; } } @Override protected void commonAssignmentCheck( AnnotatedTypeMirror varType, AnnotatedTypeMirror valueType, Tree valueTree, /*@CompilerMessageKey*/ String errorKey) { AnnotationMirror rhs = valueType.getAnnotationInHierarchy(atypeFactory.I18NUNKNOWNFORMAT); AnnotationMirror lhs = varType.getAnnotationInHierarchy(atypeFactory.I18NUNKNOWNFORMAT); // i18nformat.missing.arguments and i18nformat.excess.arguments are issued here for assignments. // For method calls, they are issued in checkInvocationFormatFor. if (AnnotationUtils.areSameIgnoringValues(rhs, atypeFactory.I18NFORMAT) && AnnotationUtils.areSameIgnoringValues(lhs, atypeFactory.I18NFORMAT)) { I18nConversionCategory[] rhsArgTypes = atypeFactory.treeUtil.formatAnnotationToCategories(rhs); I18nConversionCategory[] lhsArgTypes = atypeFactory.treeUtil.formatAnnotationToCategories(lhs); if (rhsArgTypes.length < lhsArgTypes.length) { // From the manual: // It is legal to use a format string with fewer format specifiers // than required, but a warning is issued. checker.report( org.checkerframework.framework.source.Result.warning( "i18nformat.missing.arguments", varType.toString(), valueType.toString()), valueTree); } else if (rhsArgTypes.length > lhsArgTypes.length) { // Since it is known that too many conversion categories were provided, // issue a more specific error message to that effect than assignment.type.incompatible. checker.report( org.checkerframework.framework.source.Result.failure( "i18nformat.excess.arguments", varType.toString(), valueType.toString()), valueTree); } } // By calling super.commonAssignmentCheck last, any i18nformat.excess.arguments message // issued for a given line of code will take precedence over the assignment.type.incompatible // issued by super.commonAssignmentCheck. super.commonAssignmentCheck(varType, valueType, valueTree, errorKey); } }