checker.src.org.checkerframework.checker.i18nformatter.I18nFormatterTreeUtil 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.i18nformatter;
/*>>>
import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
*/
import org.checkerframework.checker.formatter.FormatterTreeUtil.InvocationType;
import org.checkerframework.checker.formatter.FormatterTreeUtil.Result;
import org.checkerframework.checker.i18nformatter.qual.I18nChecksFormat;
import org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory;
import org.checkerframework.checker.i18nformatter.qual.I18nFormat;
import org.checkerframework.checker.i18nformatter.qual.I18nFormatFor;
import org.checkerframework.checker.i18nformatter.qual.I18nInvalidFormat;
import org.checkerframework.checker.i18nformatter.qual.I18nMakeFormat;
import org.checkerframework.checker.i18nformatter.qual.I18nValidFormat;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.dataflow.analysis.FlowExpressions.Receiver;
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.dataflow.cfg.node.StringLiteralNode;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
import org.checkerframework.framework.util.AnnotationBuilder;
import org.checkerframework.framework.util.FlowExpressionParseUtil;
import org.checkerframework.framework.util.FlowExpressionParseUtil.FlowExpressionContext;
import org.checkerframework.framework.util.FlowExpressionParseUtil.FlowExpressionParseException;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.TreeUtils;
import java.util.List;
import java.util.Map;
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.element.VariableElement;
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 I18nFormatters.
*
* @checker_framework.manual #i18n-formatter-checker Internationalization
* Format String Checker
* @author Siwakorn Srisakaokul
*/
public class I18nFormatterTreeUtil {
public final BaseTypeChecker checker;
public final ProcessingEnvironment processingEnv;
public I18nFormatterTreeUtil(BaseTypeChecker checker) {
this.checker = checker;
this.processingEnv = checker.getProcessingEnvironment();
}
/**
* Describe the format annotation type
*
*/
public enum FormatType {
I18NINVALID, I18NFORMAT, I18NFORMATFOR
}
/**
* Takes an exception that describes an invalid formatter string and,
* returns a syntax trees element that represents a
* {@link I18nInvalidFormat} annotation with the exception's error message
* as value.
*/
public AnnotationMirror exceptionToInvalidFormatAnnotation(IllegalArgumentException ex) {
AnnotationBuilder builder = new AnnotationBuilder(processingEnv, I18nInvalidFormat.class.getCanonicalName());
builder.setValue("value", ex.getMessage());
return builder.build();
}
/**
* Takes a list of ConversionCategory elements, and returns a syntax tree
* element that represents a {@link I18nFormat} annotation with the list as
* value.
*/
public AnnotationMirror categoriesToFormatAnnotation(I18nConversionCategory[] args) {
AnnotationBuilder builder = new AnnotationBuilder(processingEnv, I18nFormat.class.getCanonicalName());
builder.setValue("value", args);
return builder.build();
}
/**
* Takes a syntax tree element that represents a {@link I18nFormat}
* annotation, and returns its value.
*/
public I18nConversionCategory[] formatAnnotationToCategories(AnnotationMirror anno) {
List list =
AnnotationUtils.getElementValueEnumArray(anno, "value", I18nConversionCategory.class, false);
return list.toArray(new I18nConversionCategory[] {});
}
public boolean isHasFormatCall(MethodInvocationNode node, AnnotatedTypeFactory atypeFactory) {
ExecutableElement method = node.getTarget().getMethod();
AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, I18nChecksFormat.class);
return anno != null;
}
public boolean isIsFormatCall(MethodInvocationNode node, AnnotatedTypeFactory atypeFactory) {
ExecutableElement method = node.getTarget().getMethod();
AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, I18nValidFormat.class);
return anno != null;
}
public boolean isMakeFormatCall(MethodInvocationNode node, AnnotatedTypeFactory atypeFactory) {
ExecutableElement method = node.getTarget().getMethod();
AnnotationMirror anno = atypeFactory.getDeclAnnotation(method, I18nMakeFormat.class);
return anno != null;
}
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;
}
}
/**
* Reports an error. Takes a {@link Result} to report the location.
*/
public final void failure(Result res, /*@CompilerMessageKey*/ String msg, Object... args) {
checker.report(org.checkerframework.framework.source.Result.failure(msg, args), ((ResultImpl) res).location);
}
/**
* Reports an warning. Takes a {@link Result} to report the location.
*/
public final void warning(Result res, /*@CompilerMessageKey*/ String msg, Object... args) {
checker.report(org.checkerframework.framework.source.Result.warning(msg, args), ((ResultImpl) res).location);
}
private I18nConversionCategory[] asFormatCallCategoriesLowLevel(MethodInvocationNode node) {
Node vararg = node.getArgument(1);
if (vararg instanceof ArrayCreationNode) {
List convs = ((ArrayCreationNode) vararg).getInitializers();
I18nConversionCategory[] res = new I18nConversionCategory[convs.size()];
for (int i = 0; i < convs.size(); i++) {
Node conv = convs.get(i);
if (conv instanceof FieldAccessNode) {
if (typeMirrorToClass(((FieldAccessNode) conv).getType()) == I18nConversionCategory.class) {
res[i] = I18nConversionCategory.valueOf(((FieldAccessNode) conv).getFieldName());
continue; /* avoid returning null */
}
}
return null;
}
return res;
}
return null;
}
public Result getHasFormatCallCategories(MethodInvocationNode node) {
return new ResultImpl(asFormatCallCategoriesLowLevel(node), node.getTree());
}
public Result makeFormatCallCategories(MethodInvocationNode node,
I18nFormatterAnnotatedTypeFactory atypeFactory) {
Map translations = atypeFactory.translations;
Node firstParam = node.getArgument(0);
Result ret = new ResultImpl(null, node.getTree());
// Now only work with a literal string
if (firstParam != null && (firstParam instanceof StringLiteralNode)) {
String s = ((StringLiteralNode) firstParam).getValue();
if (translations.containsKey(s)) {
String value = translations.get(s);
ret = new ResultImpl(I18nFormatUtil.formatParameterCategories(value),
node.getTree());
}
}
return ret;
}
/**
* Returns an I18nFormatCall instance, only if FormatFor is called. Otherwise, returns null.
*/
public I18nFormatCall createFormatForCall(MethodInvocationTree tree, MethodInvocationNode node, I18nFormatterAnnotatedTypeFactory atypeFactory) {
ExecutableElement method = TreeUtils.elementFromUse(tree);
AnnotatedExecutableType methodAnno = atypeFactory.getAnnotatedType(method);
for (AnnotatedTypeMirror paramType : methodAnno.getParameterTypes()) {
// find @FormatFor
if (paramType.getAnnotation(I18nFormatFor.class) != null) {
return atypeFactory.treeUtil.new I18nFormatCall(tree, node, atypeFactory);
}
}
return null;
}
/**
* Represents a format method invocation in the syntax tree.
*
* An I18nFormatCall instance can only be instantiated by createFormatForCall method
*/
public class I18nFormatCall {
private final ExpressionTree tree;
private ExpressionTree formatArg;
private final AnnotatedTypeFactory atypeFactory;
private List args;
private String invalidMessage;
private AnnotatedTypeMirror formatAnno;
public I18nFormatCall(MethodInvocationTree tree, MethodInvocationNode node, AnnotatedTypeFactory atypeFactory) {
this.tree = tree;
this.atypeFactory = atypeFactory;
List theargs = (tree).getArguments();
this.args = null;
ExecutableElement method = TreeUtils.elementFromUse(tree);
AnnotatedExecutableType methodAnno = atypeFactory.getAnnotatedType(method);
initialCheck(theargs, method, node, methodAnno);
}
@Override
public String toString() {
return this.tree.toString();
}
/**
* This method checks the validity of the FormatFor.
* If it is valid, this.args will be set to the correct parameter arguments.
* Otherwise, it will be still null.
*/
private void initialCheck(List theargs, ExecutableElement method,
MethodInvocationNode node, AnnotatedExecutableType methodAnno) {
int paramIndex = -1;
Receiver paramArg = null;
int i = 0;
for (AnnotatedTypeMirror paramType : methodAnno.getParameterTypes()) {
if (paramType.getAnnotation(I18nFormatFor.class) != null) {
this.formatArg = theargs.get(i);
this.formatAnno = atypeFactory.getAnnotatedType(formatArg);
if (!typeMirrorToClass(paramType.getUnderlyingType()).equals(String.class)) {
// Invalid FormatFor invocation
return ;
}
FlowExpressionContext flowExprContext = FlowExpressionParseUtil
.buildFlowExprContextForUse(node, checker.getContext());
String formatforArg = AnnotationUtils.getElementValue(paramType.getAnnotation(I18nFormatFor.class)
, "value", String.class, false);
if (flowExprContext != null) {
try {
paramArg = FlowExpressionParseUtil
.parse(formatforArg, flowExprContext, atypeFactory.getPath(tree));
paramIndex = flowExprContext.arguments.indexOf(paramArg);
} catch (FlowExpressionParseException e) {
// errors are reported at declaration site
}
}
break;
}
i++;
}
if (paramIndex != -1) {
VariableElement param = method.getParameters().get(paramIndex);
if (param.asType().getKind().equals(TypeKind.ARRAY)) {
this.args = theargs.subList(paramIndex, theargs.size());
} else {
this.args = theargs.subList(paramIndex, paramIndex + 1);
}
}
}
public Result getFormatType() {
FormatType type;
if (isValidFormatForInvocation()) {
if (formatAnno.hasAnnotation(I18nFormat.class)) {
type = FormatType.I18NFORMAT;
} else if (formatAnno.hasAnnotation(I18nFormatFor.class)) {
type = FormatType.I18NFORMATFOR;
} else {
type = FormatType.I18NINVALID;
invalidMessage = "(is a @I18nFormat annotation missing?)";
AnnotationMirror inv = formatAnno.getAnnotation(I18nInvalidFormat.class);
if (inv != null) {
invalidMessage = AnnotationUtils.getElementValue(inv, "value", String.class, true);
}
}
} else {
// If the FormatFor is invalid, it's still I18nFormatFor type but invalid,
// and we can't do anything else
type = FormatType.I18NFORMATFOR;
}
return new ResultImpl(type, formatArg);
}
public final String getInvalidError() {
return invalidMessage;
}
public boolean isValidFormatForInvocation() {
return this.args != null;
}
/**
* 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;
loc = ((MethodInvocationTree) tree).getMethodSelect();
if (type != InvocationType.VARARG && args.size() > 0) {
loc = args.get(0);
}
return new ResultImpl(type, loc);
}
public Result getInvalidInvocationType() {
return new ResultImpl(FormatType.I18NFORMATFOR, formatArg);
}
/**
* Returns the conversion category for every parameter.
*
* @see I18nConversionCategory
*/
public final I18nConversionCategory[] getFormatCategories() {
AnnotationMirror anno = formatAnno.getAnnotation(I18nFormat.class);
return formatAnnotationToCategories(anno);
}
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;
}
public boolean isValidParameter(I18nConversionCategory 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;
}
}
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);
}
}