| package org.checkerframework.checker.formatter; |
| |
| import com.sun.source.tree.LiteralTree; |
| import com.sun.source.tree.Tree; |
| import java.util.IllegalFormatException; |
| import java.util.Set; |
| import javax.lang.model.element.AnnotationMirror; |
| import org.checkerframework.checker.formatter.qual.ConversionCategory; |
| import org.checkerframework.checker.formatter.qual.Format; |
| import org.checkerframework.checker.formatter.qual.FormatBottom; |
| import org.checkerframework.checker.formatter.qual.FormatMethod; |
| import org.checkerframework.checker.formatter.qual.InvalidFormat; |
| import org.checkerframework.checker.formatter.qual.UnknownFormat; |
| import org.checkerframework.checker.formatter.util.FormatUtil; |
| import org.checkerframework.checker.signature.qual.CanonicalName; |
| import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; |
| import org.checkerframework.common.basetype.BaseTypeChecker; |
| import org.checkerframework.common.wholeprograminference.WholeProgramInferenceJavaParserStorage; |
| import org.checkerframework.framework.type.AnnotatedTypeFactory; |
| import org.checkerframework.framework.type.AnnotatedTypeMirror; |
| import org.checkerframework.framework.type.MostlyNoElementQualifierHierarchy; |
| import org.checkerframework.framework.type.QualifierHierarchy; |
| import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator; |
| import org.checkerframework.framework.type.treeannotator.TreeAnnotator; |
| import org.checkerframework.framework.util.QualifierKind; |
| import org.checkerframework.javacutil.AnnotationBuilder; |
| import org.checkerframework.javacutil.AnnotationUtils; |
| import org.checkerframework.javacutil.BugInCF; |
| import scenelib.annotations.Annotation; |
| import scenelib.annotations.el.AField; |
| import scenelib.annotations.el.AMethod; |
| |
| /** |
| * Adds {@link Format} to the type of tree, if it is a {@code String} or {@code char} literal that |
| * represents a satisfiable format. The annotation's value is set to be a list of appropriate {@link |
| * ConversionCategory} values for every parameter of the format. |
| * |
| * @see ConversionCategory |
| */ |
| public class FormatterAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { |
| |
| /** The @{@link UnknownFormat} annotation. */ |
| protected final AnnotationMirror UNKNOWNFORMAT = |
| AnnotationBuilder.fromClass(elements, UnknownFormat.class); |
| /** The @{@link FormatBottom} annotation. */ |
| protected final AnnotationMirror FORMATBOTTOM = |
| AnnotationBuilder.fromClass(elements, FormatBottom.class); |
| /** The @{@link FormatMethod} annotation. */ |
| protected final AnnotationMirror FORMATMETHOD = |
| AnnotationBuilder.fromClass(elements, FormatMethod.class); |
| |
| /** The fully-qualified name of the {@link Format} qualifier. */ |
| protected static final @CanonicalName String FORMAT_NAME = Format.class.getCanonicalName(); |
| /** The fully-qualified name of the {@link InvalidFormat} qualifier. */ |
| protected static final @CanonicalName String INVALIDFORMAT_NAME = |
| InvalidFormat.class.getCanonicalName(); |
| |
| /** Syntax tree utilities. */ |
| protected final FormatterTreeUtil treeUtil = new FormatterTreeUtil(checker); |
| |
| /** Creates a FormatterAnnotatedTypeFactory. */ |
| public FormatterAnnotatedTypeFactory(BaseTypeChecker checker) { |
| super(checker); |
| |
| addAliasedDeclAnnotation( |
| com.google.errorprone.annotations.FormatMethod.class, FormatMethod.class, FORMATMETHOD); |
| |
| this.postInit(); |
| } |
| |
| @Override |
| public QualifierHierarchy createQualifierHierarchy() { |
| return new FormatterQualifierHierarchy(); |
| } |
| |
| @Override |
| protected TreeAnnotator createTreeAnnotator() { |
| return new ListTreeAnnotator(super.createTreeAnnotator(), new FormatterTreeAnnotator(this)); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * <p>If a method is annotated with {@code @FormatMethod}, remove any {@code @Format} annotation |
| * from its first argument. |
| */ |
| @Override |
| public void prepareMethodForWriting(AMethod method) { |
| if (hasFormatMethodAnno(method)) { |
| AField param = method.parameters.get(0); |
| if (param != null) { |
| Set<Annotation> paramTypeAnnos = param.type.tlAnnotationsHere; |
| paramTypeAnnos.removeIf( |
| a -> a.def.name.equals("org.checkerframework.checker.formatter.qual.Format")); |
| } |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * <p>If a method is annotated with {@code @FormatMethod}, remove any {@code @Format} annotation |
| * from its first argument. |
| */ |
| @Override |
| public void prepareMethodForWriting( |
| WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos) { |
| if (hasFormatMethodAnno(methodAnnos)) { |
| AnnotatedTypeMirror atm = methodAnnos.getParameterType(0); |
| atm.removeAnnotationByClass(org.checkerframework.checker.formatter.qual.Format.class); |
| } |
| } |
| |
| /** |
| * Returns true if the method has a {@code @FormatMethod} annotation. |
| * |
| * @param methodAnnos method annotations |
| * @return true if the method has a {@code @FormatMethod} annotation |
| */ |
| private boolean hasFormatMethodAnno(AMethod methodAnnos) { |
| for (Annotation anno : methodAnnos.tlAnnotationsHere) { |
| String annoName = anno.def.name; |
| if (annoName.equals("org.checkerframework.checker.formatter.qual.FormatMethod") |
| || anno.def.name.equals("com.google.errorprone.annotations.FormatMethod")) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Returns true if the method has a {@code @FormatMethod} annotation. |
| * |
| * @param methodAnnos method annotations |
| * @return true if the method has a {@code @FormatMethod} annotation |
| */ |
| private boolean hasFormatMethodAnno( |
| WholeProgramInferenceJavaParserStorage.CallableDeclarationAnnos methodAnnos) { |
| Set<AnnotationMirror> declarationAnnos = methodAnnos.getDeclarationAnnotations(); |
| return AnnotationUtils.containsSameByClass( |
| declarationAnnos, org.checkerframework.checker.formatter.qual.FormatMethod.class) |
| || AnnotationUtils.containsSameByName( |
| declarationAnnos, "com.google.errorprone.annotations.FormatMethod"); |
| } |
| |
| /** The tree annotator for the Format String Checker. */ |
| private class FormatterTreeAnnotator extends TreeAnnotator { |
| /** |
| * Create the tree annotator for the Format String Checker. |
| * |
| * @param atypeFactory the Format String Checker type factory |
| */ |
| public FormatterTreeAnnotator(AnnotatedTypeFactory atypeFactory) { |
| super(atypeFactory); |
| } |
| |
| @Override |
| public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) { |
| if (!type.isAnnotatedInHierarchy(UNKNOWNFORMAT)) { |
| String format = null; |
| if (tree.getKind() == Tree.Kind.STRING_LITERAL) { |
| format = (String) tree.getValue(); |
| } else if (tree.getKind() == Tree.Kind.CHAR_LITERAL) { |
| format = Character.toString((Character) tree.getValue()); |
| } |
| if (format != null) { |
| AnnotationMirror anno; |
| try { |
| ConversionCategory[] cs = FormatUtil.formatParameterCategories(format); |
| anno = FormatterAnnotatedTypeFactory.this.treeUtil.categoriesToFormatAnnotation(cs); |
| } catch (IllegalFormatException e) { |
| anno = |
| FormatterAnnotatedTypeFactory.this.treeUtil.exceptionToInvalidFormatAnnotation(e); |
| } |
| type.addAnnotation(anno); |
| } |
| } |
| return super.visitLiteral(tree, type); |
| } |
| } |
| |
| /** Qualifier hierarchy for the Formatter Checker. */ |
| class FormatterQualifierHierarchy extends MostlyNoElementQualifierHierarchy { |
| |
| /** Qualifier kind for the @{@link Format} annotation. */ |
| private final QualifierKind FORMAT_KIND; |
| |
| /** Qualifier kind for the @{@link InvalidFormat} annotation. */ |
| private final QualifierKind INVALIDFORMAT_KIND; |
| |
| /** Creates a {@link FormatterQualifierHierarchy}. */ |
| public FormatterQualifierHierarchy() { |
| super(FormatterAnnotatedTypeFactory.this.getSupportedTypeQualifiers(), elements); |
| FORMAT_KIND = getQualifierKind(FORMAT_NAME); |
| INVALIDFORMAT_KIND = getQualifierKind(INVALIDFORMAT_NAME); |
| } |
| |
| @Override |
| protected boolean isSubtypeWithElements( |
| AnnotationMirror subAnno, |
| QualifierKind subKind, |
| AnnotationMirror superAnno, |
| QualifierKind superKind) { |
| if (subKind == FORMAT_KIND && superKind == FORMAT_KIND) { |
| ConversionCategory[] rhsArgTypes = treeUtil.formatAnnotationToCategories(subAnno); |
| ConversionCategory[] lhsArgTypes = treeUtil.formatAnnotationToCategories(superAnno); |
| |
| if (rhsArgTypes.length > lhsArgTypes.length) { |
| return false; |
| } |
| |
| for (int i = 0; i < rhsArgTypes.length; ++i) { |
| if (!ConversionCategory.isSubsetOf(lhsArgTypes[i], rhsArgTypes[i])) { |
| return false; |
| } |
| } |
| return true; |
| } else if (subKind == INVALIDFORMAT_KIND && superKind == INVALIDFORMAT_KIND) { |
| return true; |
| } |
| throw new BugInCF("Unexpected kinds: %s %s", subKind, superKind); |
| } |
| |
| @Override |
| protected AnnotationMirror leastUpperBoundWithElements( |
| AnnotationMirror anno1, |
| QualifierKind qualifierKind1, |
| AnnotationMirror anno2, |
| QualifierKind qualifierKind2, |
| QualifierKind lubKind) { |
| if (qualifierKind1.isBottom()) { |
| return anno2; |
| } else if (qualifierKind2.isBottom()) { |
| return anno1; |
| } else if (qualifierKind1 == FORMAT_KIND && qualifierKind2 == FORMAT_KIND) { |
| ConversionCategory[] shorterArgTypesList = treeUtil.formatAnnotationToCategories(anno1); |
| ConversionCategory[] longerArgTypesList = treeUtil.formatAnnotationToCategories(anno2); |
| if (shorterArgTypesList.length > longerArgTypesList.length) { |
| ConversionCategory[] temp = longerArgTypesList; |
| longerArgTypesList = shorterArgTypesList; |
| shorterArgTypesList = temp; |
| } |
| |
| // From the manual: |
| // It is legal to use a format string with fewer format specifiers |
| // than required, but a warning is issued. |
| |
| ConversionCategory[] resultArgTypes = new ConversionCategory[longerArgTypesList.length]; |
| |
| for (int i = 0; i < shorterArgTypesList.length; ++i) { |
| resultArgTypes[i] = |
| ConversionCategory.intersect(shorterArgTypesList[i], longerArgTypesList[i]); |
| } |
| for (int i = shorterArgTypesList.length; i < longerArgTypesList.length; ++i) { |
| resultArgTypes[i] = longerArgTypesList[i]; |
| } |
| return treeUtil.categoriesToFormatAnnotation(resultArgTypes); |
| } else if (qualifierKind1 == INVALIDFORMAT_KIND && qualifierKind2 == INVALIDFORMAT_KIND) { |
| |
| assert !anno1.getElementValues().isEmpty(); |
| assert !anno2.getElementValues().isEmpty(); |
| |
| if (AnnotationUtils.areSame(anno1, anno2)) { |
| return anno1; |
| } |
| |
| return treeUtil.stringToInvalidFormatAnnotation( |
| "(" |
| + treeUtil.invalidFormatAnnotationToErrorMessage(anno1) |
| + " or " |
| + treeUtil.invalidFormatAnnotationToErrorMessage(anno2) |
| + ")"); |
| } |
| |
| return UNKNOWNFORMAT; |
| } |
| |
| @Override |
| protected AnnotationMirror greatestLowerBoundWithElements( |
| AnnotationMirror anno1, |
| QualifierKind qualifierKind1, |
| AnnotationMirror anno2, |
| QualifierKind qualifierKind2, |
| QualifierKind glbKind) { |
| if (qualifierKind1.isTop()) { |
| return anno2; |
| } else if (qualifierKind2.isTop()) { |
| return anno1; |
| } else if (qualifierKind1 == FORMAT_KIND && qualifierKind2 == FORMAT_KIND) { |
| ConversionCategory[] anno1ArgTypes = treeUtil.formatAnnotationToCategories(anno1); |
| ConversionCategory[] anno2ArgTypes = treeUtil.formatAnnotationToCategories(anno2); |
| |
| // From the manual: |
| // It is legal to use a format string with fewer format specifiers |
| // than required, but a warning is issued. |
| int length = anno1ArgTypes.length; |
| if (anno2ArgTypes.length < length) { |
| length = anno2ArgTypes.length; |
| } |
| |
| ConversionCategory[] anno3ArgTypes = new ConversionCategory[length]; |
| |
| for (int i = 0; i < length; ++i) { |
| anno3ArgTypes[i] = ConversionCategory.union(anno1ArgTypes[i], anno2ArgTypes[i]); |
| } |
| return treeUtil.categoriesToFormatAnnotation(anno3ArgTypes); |
| } else if (qualifierKind1 == INVALIDFORMAT_KIND && qualifierKind2 == INVALIDFORMAT_KIND) { |
| |
| assert !anno1.getElementValues().isEmpty(); |
| assert !anno2.getElementValues().isEmpty(); |
| |
| if (AnnotationUtils.areSame(anno1, anno2)) { |
| return anno1; |
| } |
| |
| return treeUtil.stringToInvalidFormatAnnotation( |
| "(" |
| + treeUtil.invalidFormatAnnotationToErrorMessage(anno1) |
| + " and " |
| + treeUtil.invalidFormatAnnotationToErrorMessage(anno2) |
| + ")"); |
| } |
| |
| return FORMATBOTTOM; |
| } |
| } |
| } |