
org.checkerframework.checker.formatter.FormatterTreeUtil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of checker Show documentation
Show all versions of checker Show documentation
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.
package org.checkerframework.checker.formatter;
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;
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.checker.nullness.qual.Nullable;
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.javacutil.AnnotationBuilder;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;
import java.util.IllegalFormatException;
import java.util.List;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.NullType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.SimpleTypeVisitor8;
/**
* This class provides a collection of utilities to ease working with syntax trees that have
* something to do with Formatters.
*/
public class FormatterTreeUtil {
/** The checker. */
public final BaseTypeChecker checker;
/** The processing environment. */
public final ProcessingEnvironment processingEnv;
/** The value() element/field of an @Format annotation. */
protected final ExecutableElement formatValueElement;
/** The value() element/field of an @InvalidFormat annotation. */
protected final ExecutableElement invalidFormatValueElement;
// private final ExecutableElement formatArgTypesElement;
public FormatterTreeUtil(BaseTypeChecker checker) {
this.checker = checker;
this.processingEnv = checker.getProcessingEnvironment();
formatValueElement = TreeUtils.getMethod(Format.class, "value", 0, processingEnv);
invalidFormatValueElement =
TreeUtils.getMethod(InvalidFormat.class, "value", 0, processingEnv);
/*
this.formatArgTypesElement =
TreeUtils.getMethod(
Format.class,
"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;
}
/** A wrapper around a value of type E, plus an ExpressionTree location. */
public static class Result {
private final E value;
public final ExpressionTree location;
public Result(E value, ExpressionTree location) {
this.value = value;
this.location = location;
}
public E value() {
return value;
}
}
/**
* Returns true if the call is to a method with the @ReturnsFormat annotation. An example of
* such a method is FormatUtil.asFormat.
*/
public boolean isAsFormatCall(MethodInvocationNode node, AnnotatedTypeFactory atypeFactory) {
ExecutableElement method = node.getTarget().getMethod();
AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, ReturnsFormat.class);
return anno != null;
}
private ConversionCategory @Nullable [] asFormatCallCategoriesLowLevel(
MethodInvocationNode node) {
Node vararg = node.getArgument(1);
if (!(vararg instanceof ArrayCreationNode)) {
return null;
}
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 extends Object> clazz =
TypesUtils.getClassFromType(((FieldAccessNode) conv).getType());
if (clazz == ConversionCategory.class) {
res[i] = ConversionCategory.valueOf(((FieldAccessNode) conv).getFieldName());
continue; /* avoid returning null */
}
}
return null;
}
return res;
}
public Result asFormatCallCategories(MethodInvocationNode node) {
// TODO make sure the method signature looks good
return new Result<>(asFormatCallCategoriesLowLevel(node), node.getTree());
}
/**
* Returns true if {@code tree} is a call to a method annotated with {@code @FormatMethod}.
*
* @param tree a method call
* @param atypeFactory a type factory
* @return true if {@code tree} is a call to a method annotated with {@code @FormatMethod}
*/
public boolean isFormatMethodCall(
MethodInvocationTree tree, AnnotatedTypeFactory atypeFactory) {
ExecutableElement method = TreeUtils.elementFromUse(tree);
AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, FormatMethod.class);
return anno != null;
}
/**
* Creates a new FormatCall, or returns null.
*
* @param invocationTree a method invocation, where the method is annotated @FormatMethod
* @param atypeFactory the type factory
* @return a new FormatCall, or null if the invocation is of a method that is improperly
* annotated @FormatMethod
*/
public @Nullable FormatCall create(
MethodInvocationTree invocationTree, AnnotatedTypeFactory atypeFactory) {
FormatterTreeUtil ftu = ((FormatterAnnotatedTypeFactory) atypeFactory).treeUtil;
if (!ftu.isFormatMethodCall(invocationTree, atypeFactory)) {
return null;
}
ExecutableElement methodElement = TreeUtils.elementFromUse(invocationTree);
int formatStringIndex = FormatterVisitor.formatStringIndex(methodElement);
if (formatStringIndex == -1) {
// Reporting the error is redundant if the method was declared in source code, because
// the visitor will have reported it; but it is necessary if the method was declared in
// byte code.
atypeFactory
.getChecker()
.reportError(
invocationTree, "format.method.invalid", methodElement.getSimpleName());
return null;
}
ExpressionTree formatStringTree = invocationTree.getArguments().get(formatStringIndex);
AnnotatedTypeMirror formatStringType = atypeFactory.getAnnotatedType(formatStringTree);
List extends ExpressionTree> allArgs = invocationTree.getArguments();
List extends ExpressionTree> args =
allArgs.subList(formatStringIndex + 1, allArgs.size());
return new FormatCall(
invocationTree, formatStringTree, formatStringType, args, atypeFactory);
}
/** Represents a format method invocation in the syntax tree. */
public class FormatCall {
/** The call itself. */
/*package-private*/ final MethodInvocationTree invocationTree;
/** The format string argument. */
private final ExpressionTree formatStringTree;
/** The type of the format string argument. */
private final AnnotatedTypeMirror formatStringType;
/** The arguments that follow the format string argument. */
private final List extends ExpressionTree> args;
/** The type factory. */
private final AnnotatedTypeFactory atypeFactory;
/**
* Create a new FormatCall object.
*
* @param invocationTree the call itself
* @param formatStringTree the format string argument
* @param formatStringType the type of the format string argument
* @param args the arguments that follow the format string argument
* @param atypeFactory the type factory
*/
private FormatCall(
MethodInvocationTree invocationTree,
ExpressionTree formatStringTree,
AnnotatedTypeMirror formatStringType,
List extends ExpressionTree> args,
AnnotatedTypeFactory atypeFactory) {
this.invocationTree = invocationTree;
this.formatStringTree = formatStringTree;
this.formatStringType = formatStringType;
this.args = args;
this.atypeFactory = atypeFactory;
}
/**
* Returns an error description if the format-string argument's type is not
* annotated as {@code @Format}. Returns null if it is annotated.
*
* @return an error description if the format string is not annotated as {@code @Format}, or
* null if it is
*/
public final @Nullable Result errMissingFormatAnnotation() {
if (!formatStringType.hasAnnotation(Format.class)) {
String msg = "(is a @Format annotation missing?)";
AnnotationMirror inv = formatStringType.getAnnotation(InvalidFormat.class);
if (inv != null) {
msg = invalidFormatAnnotationToErrorMessage(inv);
}
return new Result<>(msg, formatStringTree);
}
return null;
}
/**
* Returns the type of method invocation.
*
* @see InvocationType
*/
public final Result getInvocationType() {
InvocationType type = InvocationType.VARARG;
if (args.size() == 1) {
ExpressionTree first = args.get(0);
TypeMirror argType = atypeFactory.getAnnotatedType(first).getUnderlyingType();
// figure out if argType is an array
type =
argType.accept(
new SimpleTypeVisitor8>() {
@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<
InvocationType, Class>() {
@Override
protected InvocationType defaultAction(
Tree tree, Class p) {
// just a normal array
return InvocationType.ARRAY;
}
@Override
public InvocationType visitTypeCast(
TypeCastTree tree, Class p) {
// it's a (Object[])null
return atypeFactory
.getAnnotatedType(
tree
.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 = invocationTree.getMethodSelect();
if (type != InvocationType.VARARG && !args.isEmpty()) {
loc = args.get(0);
}
return new Result<>(type, loc);
}
/**
* Returns the conversion category for every parameter.
*
* @return the conversion categories of all the parameters
* @see ConversionCategory
*/
public final ConversionCategory[] getFormatCategories() {
AnnotationMirror anno = formatStringType.getAnnotation(Format.class);
return formatAnnotationToCategories(anno);
}
/**
* Returns the types of the arguments to the call. Use {@link #isValidArgument} and {@link
* #isArgumentNull} to work with the result.
*
* @return the types of the arguments to the call
*/
public final Result[] getArgTypes() {
// One to suppress warning in javac, the other to suppress warning in Eclipse...
@SuppressWarnings({"rawtypes", "unchecked"})
Result[] res = new Result[args.size()];
for (int i = 0; i < res.length; ++i) {
ExpressionTree arg = args.get(i);
TypeMirror argType;
if (TreeUtils.isNullExpression(arg)) {
argType = atypeFactory.getProcessingEnv().getTypeUtils().getNullType();
} else {
argType = atypeFactory.getAnnotatedType(arg).getUnderlyingType();
}
res[i] = new Result<>(argType, arg);
}
return res;
}
/**
* Checks if the type of an argument returned from {@link #getArgTypes()} is valid for the
* passed ConversionCategory.
*
* @param formatCat a format specifier
* @param argType an argument type
* @return true if the argument can be passed to the format specifier
*/
public final boolean isValidArgument(ConversionCategory formatCat, TypeMirror argType) {
if (argType.getKind() == TypeKind.NULL || isArgumentNull(argType)) {
return true;
}
Class extends Object> type = TypesUtils.getClassFromType(argType);
return formatCat.isAssignableFrom(type);
}
/**
* Checks if the argument returned from {@link #getArgTypes()} is a {@code null} expression.
*
* @param type a type
* @return true if the argument is a {@code null} expression
*/
public final boolean isArgumentNull(TypeMirror type) {
// TODO: Just check whether it is the VOID TypeMirror.
// is it the null literal
return type.accept(
new SimpleTypeVisitor8>() {
@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);
}
}
// The failure() method is required so that FormatterTransfer, which has no access to the
// FormatterChecker, can report errors.
/**
* Reports an error.
*
* @param res used for source location information
* @param msgKey the diagnostic message key
* @param args arguments to the diagnostic message
*/
public final void failure(Result> res, @CompilerMessageKey String msgKey, Object... args) {
checker.reportError(res.location, msgKey, args);
}
/**
* Reports a warning.
*
* @param res used for source location information
* @param msgKey the diagnostic message key
* @param args arguments to the diagnostic message
*/
public final void warning(Result> res, @CompilerMessageKey String msgKey, Object... args) {
checker.reportWarning(res.location, msgKey, args);
}
/**
* 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) {
return stringToInvalidFormatAnnotation(ex.getMessage());
}
/**
* Creates an {@link InvalidFormat} annotation with the given string as its value.
*
* @param invalidFormatString an invalid formatter string
* @return an {@link InvalidFormat} annotation with the given string as its value
*/
/*package-private*/ AnnotationMirror stringToInvalidFormatAnnotation(
String invalidFormatString) {
AnnotationBuilder builder = new AnnotationBuilder(processingEnv, InvalidFormat.class);
builder.setValue("value", invalidFormatString);
return builder.build();
}
/**
* Gets the value() element/field out of an InvalidFormat annotation.
*
* @param anno an InvalidFormat annotation
* @return its value() element/field
*/
private String getInvalidFormatValue(AnnotationMirror anno) {
return (String) anno.getElementValues().get(invalidFormatValueElement).getValue();
}
/**
* Takes a syntax tree element that represents a {@link InvalidFormat} annotation, and returns
* its value.
*
* @param anno an InvalidFormat annotation
* @return its value() element/field
*/
public String invalidFormatAnnotationToErrorMessage(AnnotationMirror anno) {
return "\"" + getInvalidFormatValue(anno) + "\"";
}
/**
* Creates a {@code @}{@link Format} annotation with the given list as its value.
*
* @param args conversion categories for the {@code @Format} annotation
* @return a {@code @}{@link Format} annotation with the given list as its value
*/
public AnnotationMirror categoriesToFormatAnnotation(ConversionCategory[] args) {
AnnotationBuilder builder = new AnnotationBuilder(processingEnv, Format.class);
builder.setValue("value", args);
return builder.build();
}
/**
* Returns the value of a {@code @}{@link Format} annotation.
*
* @param anno a {@code @}{@link Format} annotation
* @return the annotation's {@code value} element
*/
@SuppressWarnings("GetClassOnEnum")
public ConversionCategory[] formatAnnotationToCategories(AnnotationMirror anno) {
return AnnotationUtils.getElementValueEnumArray(
anno, formatValueElement, ConversionCategory.class);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy