package org.checkerframework.checker.i18nformatter; import javax.lang.model.element.AnnotationMirror; import org.checkerframework.checker.formatter.FormatterTreeUtil.Result; import org.checkerframework.checker.i18nformatter.qual.I18nConversionCategory; import org.checkerframework.checker.i18nformatter.qual.I18nInvalidFormat; import org.checkerframework.dataflow.analysis.ConditionalTransferResult; import org.checkerframework.dataflow.analysis.FlowExpressions; import org.checkerframework.dataflow.analysis.FlowExpressions.Receiver; import org.checkerframework.dataflow.analysis.RegularTransferResult; import org.checkerframework.dataflow.analysis.TransferInput; import org.checkerframework.dataflow.analysis.TransferResult; import org.checkerframework.dataflow.cfg.node.MethodInvocationNode; import org.checkerframework.framework.flow.CFAnalysis; import org.checkerframework.framework.flow.CFStore; import org.checkerframework.framework.flow.CFTransfer; import org.checkerframework.framework.flow.CFValue; import org.checkerframework.framework.util.AnnotationBuilder; /** * @checker_framework.manual #i18n-formatter-checker Internationalization Format String Checker * @author Siwakorn Srisakaokul */ public class I18nFormatterTransfer extends CFTransfer { public I18nFormatterTransfer(CFAnalysis analysis) { super(analysis); } @Override public TransferResult<CFValue, CFStore> visitMethodInvocation( MethodInvocationNode node, TransferInput<CFValue, CFStore> in) { I18nFormatterAnnotatedTypeFactory atypeFactory = (I18nFormatterAnnotatedTypeFactory) analysis.getTypeFactory(); TransferResult<CFValue, CFStore> result = super.visitMethodInvocation(node, in); I18nFormatterTreeUtil tu = atypeFactory.treeUtil; // If hasFormat is called, make sure that the format string is annotated correctly if (tu.isHasFormatCall(node, atypeFactory)) { CFStore thenStore = result.getRegularStore(); CFStore elseStore = thenStore.copy(); ConditionalTransferResult<CFValue, CFStore> newResult = new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore); Result<I18nConversionCategory[]> cats = tu.getHasFormatCallCategories(node); if (cats.value() == null) { tu.failure(cats, "i18nformat.indirect.arguments"); } else { Receiver firstParam = FlowExpressions.internalReprOf(atypeFactory, node.getArgument(0)); AnnotationMirror anno = atypeFactory.treeUtil.categoriesToFormatAnnotation(cats.value()); thenStore.insertValue(firstParam, anno); } return newResult; } // If isFormat is called, annotate the format string with I18nInvalidFormat if (tu.isIsFormatCall(node, atypeFactory)) { CFStore thenStore = result.getRegularStore(); CFStore elseStore = thenStore.copy(); ConditionalTransferResult<CFValue, CFStore> newResult = new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore); Receiver firstParam = FlowExpressions.internalReprOf(atypeFactory, node.getArgument(0)); AnnotationBuilder builder = new AnnotationBuilder( tu.processingEnv, I18nInvalidFormat.class.getCanonicalName()); // No need to set a value of @I18nInvalidFormat builder.setValue("value", ""); elseStore.insertValue(firstParam, builder.build()); return newResult; } // @I18nMakeFormat that will be used to annotate ResourceBundle.getString() // so that when the getString() method is called, this will check if the given key exist in the translation file // and annotate the result string with the correct format annotation according to the corresponding key's value if (tu.isMakeFormatCall(node, atypeFactory)) { Result<I18nConversionCategory[]> cats = tu.makeFormatCallCategories(node, atypeFactory); if (cats.value() == null) { tu.failure(cats, "i18nformat.key.not.found"); } else { AnnotationMirror anno = atypeFactory.treeUtil.categoriesToFormatAnnotation(cats.value()); CFValue newResultValue = analysis.createSingleAnnotationValue( anno, result.getResultValue().getUnderlyingType()); return new RegularTransferResult<>(newResultValue, result.getRegularStore()); } } return result; } }