package org.checkerframework.checker.formatter;
/*>>>
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.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.common.basetype.BaseTypeChecker;
import org.checkerframework.common.basetype.BaseTypeVisitor;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.javacutil.AnnotationUtils;
/**
* Whenever a format method invocation is found in the syntax tree, the following checks happen,
* read the code, seriously! (otherwise see manual 12.2)
*
* @author Konstantin Weitz
*/
public class FormatterVisitor extends BaseTypeVisitor<FormatterAnnotatedTypeFactory> {
public FormatterVisitor(BaseTypeChecker checker) {
super(checker);
}
@Override
public Void visitMethodInvocation(MethodInvocationTree node, Void p) {
FormatterTreeUtil tu = atypeFactory.treeUtil;
if (tu.isFormatCall(node, atypeFactory)) {
FormatCall fc = atypeFactory.treeUtil.new FormatCall(node, atypeFactory);
Result<String> sat = fc.isIllegalFormat();
if (sat.value() != null) {
// I.1
tu.failure(sat, "format.string.invalid", sat.value());
} else {
Result<InvocationType> invc = fc.getInvocationType();
ConversionCategory[] formatCats = fc.getFormatCategories();
switch (invc.value()) {
case VARARG:
Result<TypeMirror>[] paramTypes = fc.getParamTypes();
int paraml = paramTypes.length;
int formatl = formatCats.length;
if (paraml < formatl) {
// For assignments, format.missing.arguments is issued
// from commonAssignmentCheck.
// II.1
tu.failure(invc, "format.missing.arguments", formatl, paraml);
} else {
if (paraml > formatl) {
// II.2
tu.warning(invc, "format.excess.arguments", formatl, paraml);
}
for (int i = 0; i < formatl; ++i) {
ConversionCategory formatCat = formatCats[i];
Result<TypeMirror> param = paramTypes[i];
TypeMirror paramType = param.value();
switch (formatCat) {
case UNUSED:
// I.2
tu.warning(param, "format.argument.unused", " " + (1 + i));
break;
case NULL:
// I.3
tu.failure(param, "format.specifier.null", " " + (1 + i));
break;
case GENERAL:
break;
default:
if (!fc.isValidParameter(formatCat, paramType)) {
// II.3
tu.failure(
param,
"argument.type.incompatible",
paramType,
formatCat);
}
break;
}
}
}
break;
case NULLARRAY:
/* continue */
case ARRAY:
for (ConversionCategory cat : formatCats) {
if (cat == ConversionCategory.NULL) {
// I.3
tu.failure(invc, "format.specifier.null", "");
}
if (cat == ConversionCategory.UNUSED) {
// I.2
tu.warning(invc, "format.argument.unused", "");
}
}
// III
tu.warning(invc, "format.indirect.arguments");
break;
}
}
}
return super.visitMethodInvocation(node, p);
}
@Override
protected void commonAssignmentCheck(
AnnotatedTypeMirror varType,
AnnotatedTypeMirror valueType,
Tree valueTree,
/*@CompilerMessageKey*/ String errorKey) {
super.commonAssignmentCheck(varType, valueType, valueTree, errorKey);
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 (AnnotationUtils.areSameIgnoringValues(rhs, atypeFactory.FORMAT)
&& AnnotationUtils.areSameIgnoringValues(lhs, atypeFactory.FORMAT)) {
ConversionCategory[] rhsArgTypes =
atypeFactory.treeUtil.formatAnnotationToCategories(rhs);
ConversionCategory[] lhsArgTypes =
atypeFactory.treeUtil.formatAnnotationToCategories(lhs);
if (rhsArgTypes.length < lhsArgTypes.length) {
checker.report(
org.checkerframework.framework.source.Result.warning(
"format.missing.arguments",
varType.toString(),
valueType.toString()),
valueTree);
}
}
}
}