From 25ed7d306f69b89110227aa2457dc218881bee6d Mon Sep 17 00:00:00 2001 From: xingweitian <13183370+xingweitian@users.noreply.github.com> Date: Tue, 24 Mar 2020 04:08:51 -0400 Subject: [PATCH 1/8] Extends ValueChecker to read property files. --- .../common/value/PropertyFileHandler.java | 297 ++++++++++++++++++ .../value/ValueAnnotatedTypeFactory.java | 122 ++++++- .../common/value/ValueChecker.java | 5 +- .../common/value/ValueTransfer.java | 3 + .../common/value/qual/PropertyFile.java | 18 ++ .../common/value/qual/PropertyFileBottom.java | 15 + .../value/qual/PropertyFileUnknown.java | 14 + .../tests/ValueHandlePropertyFileTest.java | 28 ++ .../PropertyFileRead.java | 30 ++ .../value-handle-property-file/a.properties | 3 + 10 files changed, 519 insertions(+), 16 deletions(-) create mode 100644 framework/src/main/java/org/checkerframework/common/value/PropertyFileHandler.java create mode 100644 framework/src/main/java/org/checkerframework/common/value/qual/PropertyFile.java create mode 100644 framework/src/main/java/org/checkerframework/common/value/qual/PropertyFileBottom.java create mode 100644 framework/src/main/java/org/checkerframework/common/value/qual/PropertyFileUnknown.java create mode 100644 framework/src/test/java/tests/ValueHandlePropertyFileTest.java create mode 100644 framework/tests/value-handle-property-file/PropertyFileRead.java create mode 100644 framework/tests/value-handle-property-file/a.properties diff --git a/framework/src/main/java/org/checkerframework/common/value/PropertyFileHandler.java b/framework/src/main/java/org/checkerframework/common/value/PropertyFileHandler.java new file mode 100644 index 00000000000..fc09525a63a --- /dev/null +++ b/framework/src/main/java/org/checkerframework/common/value/PropertyFileHandler.java @@ -0,0 +1,297 @@ +package org.checkerframework.common.value; + +import static org.checkerframework.common.value.ValueAnnotatedTypeFactory.getStringValues; + +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.MethodInvocationTree; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.util.Collections; +import java.util.List; +import java.util.Properties; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.tools.Diagnostic.Kind; +import org.checkerframework.common.value.qual.PropertyFile; +import org.checkerframework.common.value.qual.StringVal; +import org.checkerframework.dataflow.analysis.FlowExpressions; +import org.checkerframework.dataflow.analysis.FlowExpressions.Receiver; +import org.checkerframework.dataflow.analysis.TransferResult; +import org.checkerframework.dataflow.cfg.node.MethodInvocationNode; +import org.checkerframework.dataflow.cfg.node.Node; +import org.checkerframework.framework.flow.CFStore; +import org.checkerframework.framework.flow.CFValue; +import org.checkerframework.framework.type.AnnotatedTypeMirror; +import org.checkerframework.javacutil.AnnotationBuilder; +import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.TreeUtils; + +/** + * Utility class for handling {@link java.util.Properties#getProperty(String)} and {@link + * java.util.Properties#getProperty(String, String)} invocations. + */ +public class PropertyFileHandler { + + /** The processing environment. */ + protected final ProcessingEnvironment env; + + /** The factory for constructing and looking up types. */ + protected final ValueAnnotatedTypeFactory factory; + + /** The checker, used for issuing diagnostic messages. */ + protected final ValueChecker checker; + + /** The ClassLoader.getResourceAsStream(String) method. */ + protected final ExecutableElement getResourceAsStream; + + /** The Properties.getProperty(String) method. */ + protected final ExecutableElement getProperty; + + /** The Properties.getProperty(String, String) method. */ + protected final ExecutableElement getPropertyWithDefaultValue; + + /** The Properties.load(String) method. */ + protected final ExecutableElement propertiesLoad; + + public PropertyFileHandler( + ProcessingEnvironment env, ValueAnnotatedTypeFactory factory, ValueChecker checker) { + this.env = env; + this.factory = factory; + this.checker = checker; + + getResourceAsStream = + TreeUtils.getMethod( + java.lang.ClassLoader.class.getName(), "getResourceAsStream", 1, env); + getProperty = + TreeUtils.getMethod(java.util.Properties.class.getName(), "getProperty", 1, env); + getPropertyWithDefaultValue = + TreeUtils.getMethod(java.util.Properties.class.getName(), "getProperty", 2, env); + propertiesLoad = + TreeUtils.getMethod( + java.util.Properties.class.getName(), "load", env, "java.io.InputStream"); + } + + /** + * @param node the method invocation tree + * @param annotatedTypeMirror the annotated type mirror + */ + public void handle(MethodInvocationTree node, AnnotatedTypeMirror annotatedTypeMirror) { + + if (TreeUtils.isMethodInvocation(node, getResourceAsStream, env)) { + + AnnotationMirror stringVal = getStringValFromArgument(node, 0); + + if (stringVal == null) { + return; + } + + String propFile = getValueFromStringVal(stringVal); + + if (propFile != null) { + annotatedTypeMirror.replaceAnnotation(createPropertyFileAnnotation(propFile)); + } + + } else if (TreeUtils.isMethodInvocation(node, getProperty, env)) { + + AnnotationMirror propertyFile = + factory.getReceiverType(node).getAnnotation(PropertyFile.class); + + AnnotationMirror stringAnnotation = getStringValFromArgument(node, 0); + + if (propertyFile != null && stringAnnotation != null) { + + String propFile = getValueFromPropFileAnnotation(propertyFile); + + if (propFile != null) { + + String propKey = getValueFromStringVal(stringAnnotation); + String propValue = readValueFromPropertyFile(propFile, propKey, null); + + if (propValue != null) { + annotatedTypeMirror.replaceAnnotation(createStringAnnotation(propValue)); + } + } + } + } else if (TreeUtils.isMethodInvocation(node, getPropertyWithDefaultValue, env)) { + + AnnotationMirror propFileAnnotation = + factory.getReceiverType(node).getAnnotation(PropertyFile.class); + + AnnotationMirror stringAnnotationArg0 = getStringValFromArgument(node, 0); + AnnotationMirror stringAnnotationArg1 = getStringValFromArgument(node, 1); + + if (propFileAnnotation != null && stringAnnotationArg0 != null) { + + String propFile = getValueFromPropFileAnnotation(propFileAnnotation); + + if (propFile != null) { + + String propKey = getValueFromStringVal(stringAnnotationArg0); + + String defaultValue = null; + + if (stringAnnotationArg1 != null) { + defaultValue = getValueFromStringVal(stringAnnotationArg1); + } + + String propValue = readValueFromPropertyFile(propFile, propKey, defaultValue); + + if (propValue != null) { + annotatedTypeMirror.replaceAnnotation(createStringAnnotation(propValue)); + } + } + } + } + } + + /** + * @param node the method invocation tree + * @param result the transfer result + */ + public void handle(MethodInvocationNode node, TransferResult result) { + + MethodInvocationTree methodInvocationTree = node.getTree(); + + if (TreeUtils.isMethodInvocation(methodInvocationTree, propertiesLoad, env)) { + + ExpressionTree arg0 = methodInvocationTree.getArguments().get(0); + AnnotationMirror propFileAnnotation = + factory.getAnnotatedType(arg0).getAnnotation(PropertyFile.class); + if (propFileAnnotation != null) { + String propFile = getValueFromPropFileAnnotation(propFileAnnotation); + + if (propFile != null) { + + Node receiver = node.getTarget().getReceiver(); + Receiver receiverRec = FlowExpressions.internalReprOf(factory, receiver); + propFileAnnotation = createPropertyFileAnnotation(propFile); + + if (result.containsTwoStores()) { + CFStore thenStore = result.getThenStore(); + CFStore elseStore = result.getElseStore(); + thenStore.insertValue(receiverRec, propFileAnnotation); + elseStore.insertValue(receiverRec, propFileAnnotation); + } else { + CFStore regularStore = result.getRegularStore(); + regularStore.insertValue(receiverRec, propFileAnnotation); + } + } + } + } + } + + /** + * Try to read the value of the key in the provided property file. + * + * @param propFile the property file to open + * @param key the key to find in the property file + * @param defaultValue the default value of that key + * @return the value of the key in the property file + */ + public String readValueFromPropertyFile(String propFile, String key, String defaultValue) { + + String res = null; + + try { + Properties prop = new Properties(); + + ClassLoader cl = this.getClass().getClassLoader(); + if (cl == null) { + // the class loader is null if the system class loader was + // used + cl = ClassLoader.getSystemClassLoader(); + } + InputStream in = cl.getResourceAsStream(propFile); + + if (in == null) { + // if the classloader didn't manage to load the file, try + // whether a FileInputStream works. For absolute paths this + // might help. + try { + in = new FileInputStream(propFile); + } catch (FileNotFoundException e) { + // ignore + } + } + + if (in == null) { + checker.message(Kind.WARNING, "Couldn't find the properties file: " + propFile); + return null; + } + + prop.load(in); + + if (defaultValue == null) { + res = prop.getProperty(key); + } else { + res = prop.getProperty(key, defaultValue); + } + } catch (Exception e) { + checker.message( + Kind.WARNING, "Exception in PropertyFileHandler.readPropertyFromFile: " + e); + e.printStackTrace(); + } + return res; + } + + /** + * Create a {@literal @}PropertyFile(value) annotation. + * + * @param value the value to set in the created annotation + * @return the created annotation + */ + protected AnnotationMirror createPropertyFileAnnotation(String value) { + AnnotationBuilder builder = new AnnotationBuilder(env, PropertyFile.class); + builder.setValue("value", value); + return builder.build(); + } + + /** + * Create a {@literal @}StringVal(value) annotation. + * + * @param value the value to set in the created annotation + * @return the created annotation + */ + protected AnnotationMirror createStringAnnotation(String value) { + return factory.createStringAnnotation(Collections.singletonList(value)); + } + + /** + * Get the first value of the {@literal @}StringVal() annotation. + * + * @param stringVal the {@literal @}StringVal() annotation + * @return the first value of the {@literal @}StringVal() annotation + */ + protected String getValueFromStringVal(AnnotationMirror stringVal) { + List values = getStringValues(stringVal); + if (!values.isEmpty()) { + return values.get(0); + } else { + return null; + } + } + + /** + * Get the value of the {@literal @}PropertyFile() annotation. + * + * @param propertyFile the {@literal @}PropertyFile() annotation + * @return the value of the {@literal @}PropertyFile() annotation + */ + protected static String getValueFromPropFileAnnotation(AnnotationMirror propertyFile) { + return AnnotationUtils.getElementValue(propertyFile, "value", String.class, false); + } + + /** + * Get the {@literal @}StringVal() annotation from the argument of the method invocation. + * + * @param node the method invocation tree + * @param position the position of the argument + * @return the target {@literal @}StringVal() annotation + */ + protected AnnotationMirror getStringValFromArgument(MethodInvocationTree node, int position) { + ExpressionTree arg = node.getArguments().get(position); + return factory.getAnnotatedType(arg).getAnnotation(StringVal.class); + } +} diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java index b2ee935a3be..71b5d47d01b 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java @@ -45,6 +45,9 @@ import org.checkerframework.common.value.qual.MinLen; import org.checkerframework.common.value.qual.MinLenFieldInvariant; import org.checkerframework.common.value.qual.PolyValue; +import org.checkerframework.common.value.qual.PropertyFile; +import org.checkerframework.common.value.qual.PropertyFileBottom; +import org.checkerframework.common.value.qual.PropertyFileUnknown; import org.checkerframework.common.value.qual.StaticallyExecutable; import org.checkerframework.common.value.qual.StringVal; import org.checkerframework.common.value.qual.UnknownVal; @@ -164,11 +167,18 @@ public class ValueAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { /** Helper class that holds references to special methods. */ private final ValueMethodIdentifier methods; + /** The property file handler. */ + public PropertyFileHandler propertyFileHandler = null; + public ValueAnnotatedTypeFactory(BaseTypeChecker checker) { super(checker); reportEvalWarnings = checker.hasOption(ValueChecker.REPORT_EVAL_WARNS); Range.ignoreOverflow = checker.hasOption(ValueChecker.IGNORE_RANGE_OVERFLOW); + if (checker.hasOption(ValueChecker.HANDLE_PROPERTY_FILE)) { + propertyFileHandler = + new PropertyFileHandler(getProcessingEnv(), this, (ValueChecker) checker); + } evaluator = new ReflectiveEvaluator(checker, this, reportEvalWarnings); addAliasedAnnotation("android.support.annotation.IntRange", IntRange.class, true); @@ -237,21 +247,30 @@ public AnnotationMirror canonicalAnnotation(AnnotationMirror anno) { protected Set> createSupportedTypeQualifiers() { // Because the Value Checker includes its own alias annotations, // the qualifiers have to be explicitly defined. - return new LinkedHashSet<>( - Arrays.asList( - ArrayLen.class, - ArrayLenRange.class, - IntVal.class, - IntRange.class, - BoolVal.class, - StringVal.class, - DoubleVal.class, - BottomVal.class, - UnknownVal.class, - IntRangeFromPositive.class, - IntRangeFromNonNegative.class, - IntRangeFromGTENegativeOne.class, - PolyValue.class)); + LinkedHashSet> supportedTypeQualifiers = + new LinkedHashSet<>( + Arrays.asList( + ArrayLen.class, + ArrayLenRange.class, + IntVal.class, + IntRange.class, + BoolVal.class, + StringVal.class, + DoubleVal.class, + BottomVal.class, + UnknownVal.class, + IntRangeFromPositive.class, + IntRangeFromNonNegative.class, + IntRangeFromGTENegativeOne.class, + PolyValue.class)); + if (propertyFileHandler != null) { + Collections.addAll( + supportedTypeQualifiers, + PropertyFile.class, + PropertyFileUnknown.class, + PropertyFileBottom.class); + } + return supportedTypeQualifiers; } @Override @@ -872,6 +891,31 @@ public AnnotationMirror leastUpperBound(AnnotationMirror a1, AnnotationMirror a2 */ @Override public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) { + if (propertyFileHandler != null) { + if (inPropertyFileQualifierHierarchy(subAnno) + && inPropertyFileQualifierHierarchy(superAnno)) { + if (AnnotationUtils.areSameByClass(subAnno, PropertyFileBottom.class) + || AnnotationUtils.areSameByClass( + superAnno, PropertyFileUnknown.class)) { + return true; + } else if (AnnotationUtils.areSameByClass(subAnno, PropertyFileUnknown.class) + || AnnotationUtils.areSameByClass( + superAnno, PropertyFileBottom.class)) { + return false; + } else if (AnnotationUtils.areSameByClass(subAnno, PropertyFile.class) + && AnnotationUtils.areSameByClass(superAnno, PropertyFile.class)) { + return comparePropFileElementValue(subAnno, superAnno); + } else { + throw new BugInCF("We should never reach here."); + } + } else if (inValueQualifierHierarchy(subAnno) + && inPropertyFileQualifierHierarchy(superAnno)) { + return false; + } else if (inPropertyFileQualifierHierarchy(subAnno) + && inValueQualifierHierarchy(superAnno)) { + return false; + } + } subAnno = convertSpecialIntRangeToStandardIntRange(subAnno); superAnno = convertSpecialIntRangeToStandardIntRange(superAnno); String subQual = AnnotationUtils.annotationName(subAnno); @@ -968,6 +1012,49 @@ public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) { return false; } } + + /** + * Return true if the target annotation is in the original value qualifier hierarchy. + * + * @param anno the annotation to check + * @return true if the target annotation is in the original value qualifier hierarchy + */ + private boolean inValueQualifierHierarchy(AnnotationMirror anno) { + return AnnotationUtils.areSameByName(anno, ARRAYLEN_NAME) + || AnnotationUtils.areSameByName(anno, ARRAYLENRANGE_NAME) + || AnnotationUtils.areSameByName(anno, INTVAL_NAME) + || AnnotationUtils.areSameByName(anno, INTRANGE_NAME) + || AnnotationUtils.areSameByName(anno, BOOLVAL_NAME) + || AnnotationUtils.areSameByName(anno, STRINGVAL_NAME) + || AnnotationUtils.areSameByName(anno, DOUBLEVAL_NAME) + || AnnotationUtils.areSameByName(anno, BOTTOMVAL_NAME) + || AnnotationUtils.areSameByName(anno, UNKNOWN_NAME) + || AnnotationUtils.areSameByName(anno, INTRANGE_FROMPOS_NAME) + || AnnotationUtils.areSameByName(anno, INTRANGE_FROMNONNEG_NAME) + || AnnotationUtils.areSameByName(anno, INTRANGE_FROMGTENEGONE_NAME) + || AnnotationUtils.areSameByName(anno, POLY_NAME); + } + + /** + * Return true if the target annotation is in the property file qualifier hierarchy. + * + * @param anno the annotation to check + * @return true if the target annotation is in the property file qualifier hierarchy. + */ + private boolean inPropertyFileQualifierHierarchy(AnnotationMirror anno) { + return AnnotationUtils.areSameByClass(anno, PropertyFile.class) + || AnnotationUtils.areSameByClass(anno, PropertyFileUnknown.class) + || AnnotationUtils.areSameByClass(anno, PropertyFileBottom.class); + } + + private boolean comparePropFileElementValue( + AnnotationMirror subAnno, AnnotationMirror superAnno) { + String subAnnoElementValue = + AnnotationUtils.getElementValue(subAnno, "value", String.class, false); + String superAnnoElementValue = + AnnotationUtils.getElementValue(superAnno, "value", String.class, false); + return superAnnoElementValue.equals(subAnnoElementValue); + } } /** @@ -1360,6 +1447,11 @@ private Range getRangeForMathMinMax(MethodInvocationTree tree) { @Override public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { + + if (propertyFileHandler != null) { + propertyFileHandler.handle(tree, type); + } + if (type.hasAnnotation(UNKNOWNVAL)) { Range range = getRangeForMathMinMax(tree); if (range != null) { diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueChecker.java b/framework/src/main/java/org/checkerframework/common/value/ValueChecker.java index 5bb0c83971d..2506441e38c 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueChecker.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueChecker.java @@ -22,7 +22,8 @@ @SupportedOptions({ ValueChecker.REPORT_EVAL_WARNS, ValueChecker.IGNORE_RANGE_OVERFLOW, - ValueChecker.NON_NULL_STRINGS_CONCATENATION + ValueChecker.NON_NULL_STRINGS_CONCATENATION, + ValueChecker.HANDLE_PROPERTY_FILE }) public class ValueChecker extends BaseTypeChecker { /** @@ -34,6 +35,8 @@ public class ValueChecker extends BaseTypeChecker { public static final String IGNORE_RANGE_OVERFLOW = "ignoreRangeOverflow"; /** Command-line option that assumes most expressions in String concatenations can be null. */ public static final String NON_NULL_STRINGS_CONCATENATION = "nonNullStringsConcatenation"; + /** Command-line option to enable the property file handler. */ + public static final String HANDLE_PROPERTY_FILE = "handlePropertyFile"; @Override protected BaseTypeVisitor createSourceVisitor() { diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueTransfer.java b/framework/src/main/java/org/checkerframework/common/value/ValueTransfer.java index 8035b5e0ab9..ac6bbd634c5 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueTransfer.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueTransfer.java @@ -441,6 +441,9 @@ public TransferResult visitMethodInvocation( MethodInvocationNode n, TransferInput p) { TransferResult result = super.visitMethodInvocation(n, p); refineStringAtLengthInvocation(n, result.getRegularStore()); + if (atypefactory.propertyFileHandler != null) { + atypefactory.propertyFileHandler.handle(n, result); + } return result; } diff --git a/framework/src/main/java/org/checkerframework/common/value/qual/PropertyFile.java b/framework/src/main/java/org/checkerframework/common/value/qual/PropertyFile.java new file mode 100644 index 00000000000..da34af0747d --- /dev/null +++ b/framework/src/main/java/org/checkerframework/common/value/qual/PropertyFile.java @@ -0,0 +1,18 @@ +package org.checkerframework.common.value.qual; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; + +/** + * If an object has type {@code @PropertyFile("a.property")}, then this object has type {@link + * java.io.InputStream} or {@link java.util.Properties} which loads the property file: a.property. + */ +@SubtypeOf({PropertyFileUnknown.class}) +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) +public @interface PropertyFile { + String value(); +} diff --git a/framework/src/main/java/org/checkerframework/common/value/qual/PropertyFileBottom.java b/framework/src/main/java/org/checkerframework/common/value/qual/PropertyFileBottom.java new file mode 100644 index 00000000000..824612fbbd9 --- /dev/null +++ b/framework/src/main/java/org/checkerframework/common/value/qual/PropertyFileBottom.java @@ -0,0 +1,15 @@ +package org.checkerframework.common.value.qual; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.checkerframework.framework.qual.SubtypeOf; +import org.checkerframework.framework.qual.TargetLocations; +import org.checkerframework.framework.qual.TypeUseLocation; + +@SubtypeOf({PropertyFile.class}) +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) +@TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND}) +public @interface PropertyFileBottom {} diff --git a/framework/src/main/java/org/checkerframework/common/value/qual/PropertyFileUnknown.java b/framework/src/main/java/org/checkerframework/common/value/qual/PropertyFileUnknown.java new file mode 100644 index 00000000000..f3f7a1e7c84 --- /dev/null +++ b/framework/src/main/java/org/checkerframework/common/value/qual/PropertyFileUnknown.java @@ -0,0 +1,14 @@ +package org.checkerframework.common.value.qual; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; +import org.checkerframework.framework.qual.SubtypeOf; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) +@DefaultQualifierInHierarchy +@SubtypeOf({}) +public @interface PropertyFileUnknown {} diff --git a/framework/src/test/java/tests/ValueHandlePropertyFileTest.java b/framework/src/test/java/tests/ValueHandlePropertyFileTest.java new file mode 100644 index 00000000000..4e967d0c893 --- /dev/null +++ b/framework/src/test/java/tests/ValueHandlePropertyFileTest.java @@ -0,0 +1,28 @@ +package tests; + +import java.io.File; +import java.util.List; +import org.checkerframework.common.value.ValueChecker; +import org.checkerframework.framework.test.FrameworkPerDirectoryTest; +import org.junit.runners.Parameterized.Parameters; + +/** Tests the constant value propagation type system with property file handler. */ +public class ValueHandlePropertyFileTest extends FrameworkPerDirectoryTest { + + /** @param testFiles the files containing test code, which will be type-checked */ + public ValueHandlePropertyFileTest(List testFiles) { + super( + testFiles, + org.checkerframework.common.value.ValueChecker.class, + "value", + "-Anomsgtext", + "-Astubs=statically-executable.astub", + "-A" + ValueChecker.REPORT_EVAL_WARNS, + "-A" + ValueChecker.HANDLE_PROPERTY_FILE); + } + + @Parameters + public static String[] getTestDirs() { + return new String[] {"value", "all-systems", "value-handle-property-file"}; + } +} diff --git a/framework/tests/value-handle-property-file/PropertyFileRead.java b/framework/tests/value-handle-property-file/PropertyFileRead.java new file mode 100644 index 00000000000..200af363cd0 --- /dev/null +++ b/framework/tests/value-handle-property-file/PropertyFileRead.java @@ -0,0 +1,30 @@ +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; +import org.checkerframework.common.value.qual.StringVal; + +class PropertyFileRead { + + public static final String propFile = "tests/value-handle-property-file/a.properties"; + + void a() throws IOException { + Properties prop = new Properties(); + InputStream inputStream = getClass().getClassLoader().getResourceAsStream(propFile); + prop.load(inputStream); + @StringVal("http://www.example.com") String url = prop.getProperty("URL"); + } + + void b() throws IOException { + Properties prop = new Properties(); + InputStream inputStream = getClass().getClassLoader().getResourceAsStream(propFile); + prop.load(inputStream); + @StringVal("localhost") String host = prop.getProperty("HOST", "127.0.0.1"); + } + + void c() throws IOException { + Properties prop = new Properties(); + InputStream inputStream = getClass().getClassLoader().getResourceAsStream(propFile); + prop.load(inputStream); + @StringVal("default value") String host = prop.getProperty("NOSUCHKEY", "default value"); + } +} diff --git a/framework/tests/value-handle-property-file/a.properties b/framework/tests/value-handle-property-file/a.properties new file mode 100644 index 00000000000..8ff0305fa72 --- /dev/null +++ b/framework/tests/value-handle-property-file/a.properties @@ -0,0 +1,3 @@ +URL=http://www.example.com +HOST=localhost +PORT=8080 From ed51b98479d196443416623823f3412c31c18030 Mon Sep 17 00:00:00 2001 From: xingweitian <13183370+xingweitian@users.noreply.github.com> Date: Tue, 24 Mar 2020 05:03:12 -0400 Subject: [PATCH 2/8] Add missing javadoc. Add @Documented. --- .../common/value/PropertyFileHandler.java | 7 +++++++ .../common/value/ValueAnnotatedTypeFactory.java | 12 ++++++++++++ .../common/value/qual/PropertyFile.java | 2 ++ .../common/value/qual/PropertyFileBottom.java | 3 +++ .../common/value/qual/PropertyFileUnknown.java | 3 +++ 5 files changed, 27 insertions(+) diff --git a/framework/src/main/java/org/checkerframework/common/value/PropertyFileHandler.java b/framework/src/main/java/org/checkerframework/common/value/PropertyFileHandler.java index fc09525a63a..f66d094dd7b 100644 --- a/framework/src/main/java/org/checkerframework/common/value/PropertyFileHandler.java +++ b/framework/src/main/java/org/checkerframework/common/value/PropertyFileHandler.java @@ -55,6 +55,13 @@ public class PropertyFileHandler { /** The Properties.load(String) method. */ protected final ExecutableElement propertiesLoad; + /** + * Create a new PropertyFileHandler. + * + * @param env the processing environment + * @param factory the annotated type factory + * @param checker the checker to use + */ public PropertyFileHandler( ProcessingEnvironment env, ValueAnnotatedTypeFactory factory, ValueChecker checker) { this.env = env; diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java index 71b5d47d01b..995e65a74a6 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java @@ -170,6 +170,11 @@ public class ValueAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { /** The property file handler. */ public PropertyFileHandler propertyFileHandler = null; + /** + * Create a new ValueAnnotatedTypeFactory. + * + * @param checker the checker to use + */ public ValueAnnotatedTypeFactory(BaseTypeChecker checker) { super(checker); @@ -1047,6 +1052,13 @@ private boolean inPropertyFileQualifierHierarchy(AnnotationMirror anno) { || AnnotationUtils.areSameByClass(anno, PropertyFileBottom.class); } + /** + * Compare two {@literal @}PropertyFile annotations. + * + * @param subAnno the sub annotation + * @param superAnno the super annotation + * @return true if the two annotations' values are the same + */ private boolean comparePropFileElementValue( AnnotationMirror subAnno, AnnotationMirror superAnno) { String subAnnoElementValue = diff --git a/framework/src/main/java/org/checkerframework/common/value/qual/PropertyFile.java b/framework/src/main/java/org/checkerframework/common/value/qual/PropertyFile.java index da34af0747d..d3b96765a1c 100644 --- a/framework/src/main/java/org/checkerframework/common/value/qual/PropertyFile.java +++ b/framework/src/main/java/org/checkerframework/common/value/qual/PropertyFile.java @@ -1,5 +1,6 @@ package org.checkerframework.common.value.qual; +import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -13,6 +14,7 @@ @SubtypeOf({PropertyFileUnknown.class}) @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) +@Documented public @interface PropertyFile { String value(); } diff --git a/framework/src/main/java/org/checkerframework/common/value/qual/PropertyFileBottom.java b/framework/src/main/java/org/checkerframework/common/value/qual/PropertyFileBottom.java index 824612fbbd9..a74979c3cba 100644 --- a/framework/src/main/java/org/checkerframework/common/value/qual/PropertyFileBottom.java +++ b/framework/src/main/java/org/checkerframework/common/value/qual/PropertyFileBottom.java @@ -1,5 +1,6 @@ package org.checkerframework.common.value.qual; +import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -8,8 +9,10 @@ import org.checkerframework.framework.qual.TargetLocations; import org.checkerframework.framework.qual.TypeUseLocation; +/** The bottom qualifier in PropertyFile hierarchy. */ @SubtypeOf({PropertyFile.class}) @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @TargetLocations({TypeUseLocation.EXPLICIT_LOWER_BOUND, TypeUseLocation.EXPLICIT_UPPER_BOUND}) +@Documented public @interface PropertyFileBottom {} diff --git a/framework/src/main/java/org/checkerframework/common/value/qual/PropertyFileUnknown.java b/framework/src/main/java/org/checkerframework/common/value/qual/PropertyFileUnknown.java index f3f7a1e7c84..557c13f7140 100644 --- a/framework/src/main/java/org/checkerframework/common/value/qual/PropertyFileUnknown.java +++ b/framework/src/main/java/org/checkerframework/common/value/qual/PropertyFileUnknown.java @@ -1,5 +1,6 @@ package org.checkerframework.common.value.qual; +import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -7,8 +8,10 @@ import org.checkerframework.framework.qual.DefaultQualifierInHierarchy; import org.checkerframework.framework.qual.SubtypeOf; +/** The top qualifier in PropertyFile hierarchy. */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER}) @DefaultQualifierInHierarchy @SubtypeOf({}) +@Documented public @interface PropertyFileUnknown {} From f69d82eb9730b4673cd649e0e07a93ee1dacf2ac Mon Sep 17 00:00:00 2001 From: xingweitian <13183370+xingweitian@users.noreply.github.com> Date: Fri, 27 Mar 2020 00:01:41 -0400 Subject: [PATCH 3/8] Resolve some comments. --- .../common/value/PropertyFileHandler.java | 91 +++++++++++-------- .../value/ValueAnnotatedTypeFactory.java | 31 +------ 2 files changed, 54 insertions(+), 68 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/value/PropertyFileHandler.java b/framework/src/main/java/org/checkerframework/common/value/PropertyFileHandler.java index f66d094dd7b..b7a50769975 100644 --- a/framework/src/main/java/org/checkerframework/common/value/PropertyFileHandler.java +++ b/framework/src/main/java/org/checkerframework/common/value/PropertyFileHandler.java @@ -15,9 +15,12 @@ import javax.lang.model.element.ExecutableElement; import javax.tools.Diagnostic.Kind; import org.checkerframework.common.value.qual.PropertyFile; +import org.checkerframework.common.value.qual.PropertyFileBottom; +import org.checkerframework.common.value.qual.PropertyFileUnknown; import org.checkerframework.common.value.qual.StringVal; import org.checkerframework.dataflow.analysis.FlowExpressions; import org.checkerframework.dataflow.analysis.FlowExpressions.Receiver; +import org.checkerframework.dataflow.analysis.TransferInput; import org.checkerframework.dataflow.analysis.TransferResult; import org.checkerframework.dataflow.cfg.node.MethodInvocationNode; import org.checkerframework.dataflow.cfg.node.Node; @@ -26,6 +29,7 @@ import org.checkerframework.framework.type.AnnotatedTypeMirror; import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.AnnotationUtils; +import org.checkerframework.javacutil.BugInCF; import org.checkerframework.javacutil.TreeUtils; /** @@ -81,70 +85,53 @@ public PropertyFileHandler( } /** + * Handle the property file. Used in {@link + * org.checkerframework.common.value.ValueAnnotatedTypeFactory.ValueTreeAnnotator#visitMethodInvocation(MethodInvocationTree, + * AnnotatedTypeMirror)}. + * * @param node the method invocation tree * @param annotatedTypeMirror the annotated type mirror */ public void handle(MethodInvocationTree node, AnnotatedTypeMirror annotatedTypeMirror) { - if (TreeUtils.isMethodInvocation(node, getResourceAsStream, env)) { - AnnotationMirror stringVal = getStringValFromArgument(node, 0); - if (stringVal == null) { return; } - String propFile = getValueFromStringVal(stringVal); - if (propFile != null) { annotatedTypeMirror.replaceAnnotation(createPropertyFileAnnotation(propFile)); } - } else if (TreeUtils.isMethodInvocation(node, getProperty, env)) { - AnnotationMirror propertyFile = factory.getReceiverType(node).getAnnotation(PropertyFile.class); - AnnotationMirror stringAnnotation = getStringValFromArgument(node, 0); - if (propertyFile != null && stringAnnotation != null) { - String propFile = getValueFromPropFileAnnotation(propertyFile); - if (propFile != null) { - String propKey = getValueFromStringVal(stringAnnotation); String propValue = readValueFromPropertyFile(propFile, propKey, null); - if (propValue != null) { annotatedTypeMirror.replaceAnnotation(createStringAnnotation(propValue)); } } } } else if (TreeUtils.isMethodInvocation(node, getPropertyWithDefaultValue, env)) { - AnnotationMirror propFileAnnotation = factory.getReceiverType(node).getAnnotation(PropertyFile.class); - AnnotationMirror stringAnnotationArg0 = getStringValFromArgument(node, 0); AnnotationMirror stringAnnotationArg1 = getStringValFromArgument(node, 1); - if (propFileAnnotation != null && stringAnnotationArg0 != null) { - String propFile = getValueFromPropFileAnnotation(propFileAnnotation); - if (propFile != null) { - String propKey = getValueFromStringVal(stringAnnotationArg0); - - String defaultValue = null; - + String defaultValue; if (stringAnnotationArg1 != null) { defaultValue = getValueFromStringVal(stringAnnotationArg1); + } else { + defaultValue = null; } - String propValue = readValueFromPropertyFile(propFile, propKey, defaultValue); - if (propValue != null) { annotatedTypeMirror.replaceAnnotation(createStringAnnotation(propValue)); } @@ -154,27 +141,24 @@ public void handle(MethodInvocationTree node, AnnotatedTypeMirror annotatedTypeM } /** + * Handle the property file. Used in {@link + * ValueTransfer#visitMethodInvocation(MethodInvocationNode, TransferInput)}. + * * @param node the method invocation tree * @param result the transfer result */ public void handle(MethodInvocationNode node, TransferResult result) { - MethodInvocationTree methodInvocationTree = node.getTree(); - if (TreeUtils.isMethodInvocation(methodInvocationTree, propertiesLoad, env)) { - ExpressionTree arg0 = methodInvocationTree.getArguments().get(0); AnnotationMirror propFileAnnotation = factory.getAnnotatedType(arg0).getAnnotation(PropertyFile.class); if (propFileAnnotation != null) { String propFile = getValueFromPropFileAnnotation(propFileAnnotation); - if (propFile != null) { - Node receiver = node.getTarget().getReceiver(); Receiver receiverRec = FlowExpressions.internalReprOf(factory, receiver); propFileAnnotation = createPropertyFileAnnotation(propFile); - if (result.containsTwoStores()) { CFStore thenStore = result.getThenStore(); CFStore elseStore = result.getElseStore(); @@ -189,6 +173,44 @@ public void handle(MethodInvocationNode node, TransferResult r } } + /** + * Compare two {@literal @}PropertyFile annotations. + * + * @param subAnno the sub annotation + * @param superAnno the super annotation + * @return true if the two annotations' values are the same + */ + protected boolean comparePropFileElementValue( + AnnotationMirror subAnno, AnnotationMirror superAnno) { + String subAnnoElementValue = + AnnotationUtils.getElementValue(subAnno, "value", String.class, false); + String superAnnoElementValue = + AnnotationUtils.getElementValue(superAnno, "value", String.class, false); + return superAnnoElementValue.equals(subAnnoElementValue); + } + + /** + * Tests whether subAnno is subtype of superAnno in the property file type hierarchy. + * + * @param subAnno the sub qualifier + * @param superAnno the super qualifier + * @return true if subAnno is subtype of superAnno + */ + protected boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) { + if (AnnotationUtils.areSameByClass(subAnno, PropertyFileBottom.class) + || AnnotationUtils.areSameByClass(superAnno, PropertyFileUnknown.class)) { + return true; + } else if (AnnotationUtils.areSameByClass(subAnno, PropertyFileUnknown.class) + || AnnotationUtils.areSameByClass(superAnno, PropertyFileBottom.class)) { + return false; + } else if (AnnotationUtils.areSameByClass(subAnno, PropertyFile.class) + && AnnotationUtils.areSameByClass(superAnno, PropertyFile.class)) { + return comparePropFileElementValue(subAnno, superAnno); + } else { + throw new BugInCF("We should never reach here."); + } + } + /** * Try to read the value of the key in the provided property file. * @@ -197,13 +219,10 @@ public void handle(MethodInvocationNode node, TransferResult r * @param defaultValue the default value of that key * @return the value of the key in the property file */ - public String readValueFromPropertyFile(String propFile, String key, String defaultValue) { - + protected String readValueFromPropertyFile(String propFile, String key, String defaultValue) { String res = null; - try { Properties prop = new Properties(); - ClassLoader cl = this.getClass().getClassLoader(); if (cl == null) { // the class loader is null if the system class loader was @@ -211,7 +230,6 @@ public String readValueFromPropertyFile(String propFile, String key, String defa cl = ClassLoader.getSystemClassLoader(); } InputStream in = cl.getResourceAsStream(propFile); - if (in == null) { // if the classloader didn't manage to load the file, try // whether a FileInputStream works. For absolute paths this @@ -222,14 +240,11 @@ public String readValueFromPropertyFile(String propFile, String key, String defa // ignore } } - if (in == null) { checker.message(Kind.WARNING, "Couldn't find the properties file: " + propFile); return null; } - prop.load(in); - if (defaultValue == null) { res = prop.getProperty(key); } else { diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java index 995e65a74a6..c3bd6647d93 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java @@ -899,20 +899,7 @@ public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) { if (propertyFileHandler != null) { if (inPropertyFileQualifierHierarchy(subAnno) && inPropertyFileQualifierHierarchy(superAnno)) { - if (AnnotationUtils.areSameByClass(subAnno, PropertyFileBottom.class) - || AnnotationUtils.areSameByClass( - superAnno, PropertyFileUnknown.class)) { - return true; - } else if (AnnotationUtils.areSameByClass(subAnno, PropertyFileUnknown.class) - || AnnotationUtils.areSameByClass( - superAnno, PropertyFileBottom.class)) { - return false; - } else if (AnnotationUtils.areSameByClass(subAnno, PropertyFile.class) - && AnnotationUtils.areSameByClass(superAnno, PropertyFile.class)) { - return comparePropFileElementValue(subAnno, superAnno); - } else { - throw new BugInCF("We should never reach here."); - } + return propertyFileHandler.isSubtype(subAnno, superAnno); } else if (inValueQualifierHierarchy(subAnno) && inPropertyFileQualifierHierarchy(superAnno)) { return false; @@ -1051,22 +1038,6 @@ private boolean inPropertyFileQualifierHierarchy(AnnotationMirror anno) { || AnnotationUtils.areSameByClass(anno, PropertyFileUnknown.class) || AnnotationUtils.areSameByClass(anno, PropertyFileBottom.class); } - - /** - * Compare two {@literal @}PropertyFile annotations. - * - * @param subAnno the sub annotation - * @param superAnno the super annotation - * @return true if the two annotations' values are the same - */ - private boolean comparePropFileElementValue( - AnnotationMirror subAnno, AnnotationMirror superAnno) { - String subAnnoElementValue = - AnnotationUtils.getElementValue(subAnno, "value", String.class, false); - String superAnnoElementValue = - AnnotationUtils.getElementValue(superAnno, "value", String.class, false); - return superAnnoElementValue.equals(subAnnoElementValue); - } } /** From 56c7f45be80ad0f5d7d546444926a582737c4d8f Mon Sep 17 00:00:00 2001 From: xingweitian Date: Sun, 26 Apr 2020 18:05:58 -0400 Subject: [PATCH 4/8] Resolve some of the comments. --- .../common/value/PropertyFileHandler.java | 12 +++ .../value/ValueAnnotatedTypeFactory.java | 77 ++++++++----------- .../PropertyFileRead.java | 17 ++++ 3 files changed, 61 insertions(+), 45 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/value/PropertyFileHandler.java b/framework/src/main/java/org/checkerframework/common/value/PropertyFileHandler.java index b7a50769975..def9ea3b608 100644 --- a/framework/src/main/java/org/checkerframework/common/value/PropertyFileHandler.java +++ b/framework/src/main/java/org/checkerframework/common/value/PropertyFileHandler.java @@ -84,6 +84,18 @@ public PropertyFileHandler( java.util.Properties.class.getName(), "load", env, "java.io.InputStream"); } + /** + * Return true if the target annotation is in the property file qualifier hierarchy. + * + * @param anno the annotation to check + * @return true if the target annotation is in the property file qualifier hierarchy. + */ + public static boolean inPropertyFileQualifierHierarchy(AnnotationMirror anno) { + return AnnotationUtils.areSameByClass(anno, PropertyFile.class) + || AnnotationUtils.areSameByClass(anno, PropertyFileUnknown.class) + || AnnotationUtils.areSameByClass(anno, PropertyFileBottom.class); + } + /** * Handle the property file. Used in {@link * org.checkerframework.common.value.ValueAnnotatedTypeFactory.ValueTreeAnnotator#visitMethodInvocation(MethodInvocationTree, diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java index c3bd6647d93..dbc0a7199d0 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java @@ -1,5 +1,7 @@ package org.checkerframework.common.value; +import static org.checkerframework.common.value.PropertyFileHandler.inPropertyFileQualifierHierarchy; + import com.sun.source.tree.ConditionalExpressionTree; import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.LiteralTree; @@ -170,6 +172,24 @@ public class ValueAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { /** The property file handler. */ public PropertyFileHandler propertyFileHandler = null; + /** Set of supported type qualifiers in the original value hierarchy. */ + protected static final LinkedHashSet> supportedValueQualifiers = + new LinkedHashSet<>( + Arrays.asList( + ArrayLen.class, + ArrayLenRange.class, + IntVal.class, + IntRange.class, + BoolVal.class, + StringVal.class, + DoubleVal.class, + BottomVal.class, + UnknownVal.class, + IntRangeFromPositive.class, + IntRangeFromNonNegative.class, + IntRangeFromGTENegativeOne.class, + PolyValue.class)); + /** * Create a new ValueAnnotatedTypeFactory. * @@ -253,21 +273,7 @@ protected Set> createSupportedTypeQualifiers() { // Because the Value Checker includes its own alias annotations, // the qualifiers have to be explicitly defined. LinkedHashSet> supportedTypeQualifiers = - new LinkedHashSet<>( - Arrays.asList( - ArrayLen.class, - ArrayLenRange.class, - IntVal.class, - IntRange.class, - BoolVal.class, - StringVal.class, - DoubleVal.class, - BottomVal.class, - UnknownVal.class, - IntRangeFromPositive.class, - IntRangeFromNonNegative.class, - IntRangeFromGTENegativeOne.class, - PolyValue.class)); + new LinkedHashSet<>(supportedValueQualifiers); if (propertyFileHandler != null) { Collections.addAll( supportedTypeQualifiers, @@ -900,11 +906,11 @@ public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) { if (inPropertyFileQualifierHierarchy(subAnno) && inPropertyFileQualifierHierarchy(superAnno)) { return propertyFileHandler.isSubtype(subAnno, superAnno); - } else if (inValueQualifierHierarchy(subAnno) - && inPropertyFileQualifierHierarchy(superAnno)) { - return false; - } else if (inPropertyFileQualifierHierarchy(subAnno) - && inValueQualifierHierarchy(superAnno)) { + } else if ((inValueQualifierHierarchy(subAnno) + && inPropertyFileQualifierHierarchy(superAnno)) + || (inPropertyFileQualifierHierarchy(subAnno) + && inValueQualifierHierarchy(superAnno))) { + // Fall through when two are in the different type hierarchy. return false; } } @@ -1012,31 +1018,12 @@ && inValueQualifierHierarchy(superAnno)) { * @return true if the target annotation is in the original value qualifier hierarchy */ private boolean inValueQualifierHierarchy(AnnotationMirror anno) { - return AnnotationUtils.areSameByName(anno, ARRAYLEN_NAME) - || AnnotationUtils.areSameByName(anno, ARRAYLENRANGE_NAME) - || AnnotationUtils.areSameByName(anno, INTVAL_NAME) - || AnnotationUtils.areSameByName(anno, INTRANGE_NAME) - || AnnotationUtils.areSameByName(anno, BOOLVAL_NAME) - || AnnotationUtils.areSameByName(anno, STRINGVAL_NAME) - || AnnotationUtils.areSameByName(anno, DOUBLEVAL_NAME) - || AnnotationUtils.areSameByName(anno, BOTTOMVAL_NAME) - || AnnotationUtils.areSameByName(anno, UNKNOWN_NAME) - || AnnotationUtils.areSameByName(anno, INTRANGE_FROMPOS_NAME) - || AnnotationUtils.areSameByName(anno, INTRANGE_FROMNONNEG_NAME) - || AnnotationUtils.areSameByName(anno, INTRANGE_FROMGTENEGONE_NAME) - || AnnotationUtils.areSameByName(anno, POLY_NAME); - } - - /** - * Return true if the target annotation is in the property file qualifier hierarchy. - * - * @param anno the annotation to check - * @return true if the target annotation is in the property file qualifier hierarchy. - */ - private boolean inPropertyFileQualifierHierarchy(AnnotationMirror anno) { - return AnnotationUtils.areSameByClass(anno, PropertyFile.class) - || AnnotationUtils.areSameByClass(anno, PropertyFileUnknown.class) - || AnnotationUtils.areSameByClass(anno, PropertyFileBottom.class); + for (Class each : supportedValueQualifiers) { + if (AnnotationUtils.areSameByClass(anno, each)) { + return true; + } + } + return false; } } diff --git a/framework/tests/value-handle-property-file/PropertyFileRead.java b/framework/tests/value-handle-property-file/PropertyFileRead.java index 200af363cd0..0acf19cb549 100644 --- a/framework/tests/value-handle-property-file/PropertyFileRead.java +++ b/framework/tests/value-handle-property-file/PropertyFileRead.java @@ -27,4 +27,21 @@ void c() throws IOException { prop.load(inputStream); @StringVal("default value") String host = prop.getProperty("NOSUCHKEY", "default value"); } + + void d() throws IOException { + Properties prop = new Properties(); + InputStream inputStream = getClass().getClassLoader().getResourceAsStream(propFile); + prop.load(inputStream); + // :: error: (assignment.type.incompatible) + @StringVal("8081") String port = prop.getProperty("PORT"); + } + + void e() throws IOException { + Properties prop = new Properties(); + InputStream inputStream = getClass().getClassLoader().getResourceAsStream(propFile); + prop.load(inputStream); + // :: error: (assignment.type.incompatible) + @StringVal("8081") String port = prop.getProperty("PORT"); + @StringVal("http://www.example.com") String url = prop.getProperty("URL"); + } } From b7d582932d9a944155df2c440758d9b5633a1eff Mon Sep 17 00:00:00 2001 From: xingweitian Date: Sat, 23 May 2020 01:18:31 -0400 Subject: [PATCH 5/8] Fix reference change. --- .../org/checkerframework/common/value/PropertyFileHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/main/java/org/checkerframework/common/value/PropertyFileHandler.java b/framework/src/main/java/org/checkerframework/common/value/PropertyFileHandler.java index def9ea3b608..15f55323b78 100644 --- a/framework/src/main/java/org/checkerframework/common/value/PropertyFileHandler.java +++ b/framework/src/main/java/org/checkerframework/common/value/PropertyFileHandler.java @@ -98,7 +98,7 @@ public static boolean inPropertyFileQualifierHierarchy(AnnotationMirror anno) { /** * Handle the property file. Used in {@link - * org.checkerframework.common.value.ValueAnnotatedTypeFactory.ValueTreeAnnotator#visitMethodInvocation(MethodInvocationTree, + * org.checkerframework.common.value.ValueTreeAnnotator#visitMethodInvocation(MethodInvocationTree, * AnnotatedTypeMirror)}. * * @param node the method invocation tree From 791ea01c4dd10a0b88b2d5aedc08ce734e1831df Mon Sep 17 00:00:00 2001 From: xingweitian Date: Thu, 28 May 2020 21:30:49 -0400 Subject: [PATCH 6/8] Resolve comments. --- .../common/value/PropertyFileHandler.java | 130 +++++++++++------- .../value/ValueAnnotatedTypeFactory.java | 7 +- .../common/value/ValueChecker.java | 4 +- .../common/value/ValueQualifierHierarchy.java | 23 +--- .../common/value/ValueTreeAnnotator.java | 1 - .../tests/ValueHandlePropertyFileTest.java | 2 +- 6 files changed, 91 insertions(+), 76 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/value/PropertyFileHandler.java b/framework/src/main/java/org/checkerframework/common/value/PropertyFileHandler.java index 15f55323b78..ef1a96f0594 100644 --- a/framework/src/main/java/org/checkerframework/common/value/PropertyFileHandler.java +++ b/framework/src/main/java/org/checkerframework/common/value/PropertyFileHandler.java @@ -97,7 +97,23 @@ public static boolean inPropertyFileQualifierHierarchy(AnnotationMirror anno) { } /** - * Handle the property file. Used in {@link + * Handle the property file. Refine the return type of {@link + * java.lang.ClassLoader#getResourceAsStream(String)}, {@link + * java.util.Properties#getProperty(String)}, and {@link + * java.util.Properties#getProperty(String, String)}: + * + *
    + *
  • Refine the return type of {@code getResourceAsStream(propFile)} to + * {@literal @}PropertyFile(propFile), + *
  • refine the return type of {@code property.getProperty("URL")} from + * {@literal @}UnknownVal to {@literal @}StringVal("the value of key 'URL' in the property + * file") if {@code property} has type {@literal @}PropertyFile(), and + *
  • refine the return type of {@code property.getProperty("URL", "default value")} to + * {@literal @}StringVal() if {@code property} has type {@literal @}PropertyFile(). + * Default value will be applied when the target key does not exist. + *
+ * + *

Used in {@link * org.checkerframework.common.value.ValueTreeAnnotator#visitMethodInvocation(MethodInvocationTree, * AnnotatedTypeMirror)}. * @@ -111,50 +127,60 @@ public void handle(MethodInvocationTree node, AnnotatedTypeMirror annotatedTypeM return; } String propFile = getValueFromStringVal(stringVal); - if (propFile != null) { - annotatedTypeMirror.replaceAnnotation(createPropertyFileAnnotation(propFile)); + if (propFile == null) { + return; } + annotatedTypeMirror.replaceAnnotation(createPropertyFileAnnotation(propFile)); } else if (TreeUtils.isMethodInvocation(node, getProperty, env)) { AnnotationMirror propertyFile = factory.getReceiverType(node).getAnnotation(PropertyFile.class); AnnotationMirror stringAnnotation = getStringValFromArgument(node, 0); - if (propertyFile != null && stringAnnotation != null) { - String propFile = getValueFromPropFileAnnotation(propertyFile); - if (propFile != null) { - String propKey = getValueFromStringVal(stringAnnotation); - String propValue = readValueFromPropertyFile(propFile, propKey, null); - if (propValue != null) { - annotatedTypeMirror.replaceAnnotation(createStringAnnotation(propValue)); - } - } + if (propertyFile == null || stringAnnotation == null) { + return; + } + String propFile = getValueFromPropFileAnnotation(propertyFile); + if (propFile == null) { + return; + } + String propKey = getValueFromStringVal(stringAnnotation); + String propValue = readValueFromPropertyFile(propFile, propKey, null); + if (propValue == null) { + return; } + annotatedTypeMirror.replaceAnnotation(createStringAnnotation(propValue)); } else if (TreeUtils.isMethodInvocation(node, getPropertyWithDefaultValue, env)) { AnnotationMirror propFileAnnotation = factory.getReceiverType(node).getAnnotation(PropertyFile.class); AnnotationMirror stringAnnotationArg0 = getStringValFromArgument(node, 0); AnnotationMirror stringAnnotationArg1 = getStringValFromArgument(node, 1); - if (propFileAnnotation != null && stringAnnotationArg0 != null) { - String propFile = getValueFromPropFileAnnotation(propFileAnnotation); - if (propFile != null) { - String propKey = getValueFromStringVal(stringAnnotationArg0); - String defaultValue; - if (stringAnnotationArg1 != null) { - defaultValue = getValueFromStringVal(stringAnnotationArg1); - } else { - defaultValue = null; - } - String propValue = readValueFromPropertyFile(propFile, propKey, defaultValue); - if (propValue != null) { - annotatedTypeMirror.replaceAnnotation(createStringAnnotation(propValue)); - } - } + if (propFileAnnotation == null || stringAnnotationArg0 == null) { + return; } + String propFile = getValueFromPropFileAnnotation(propFileAnnotation); + if (propFile == null) { + return; + } + String propKey = getValueFromStringVal(stringAnnotationArg0); + String defaultValue; + if (stringAnnotationArg1 != null) { + defaultValue = getValueFromStringVal(stringAnnotationArg1); + } else { + defaultValue = null; + } + String propValue = readValueFromPropertyFile(propFile, propKey, defaultValue); + if (propValue == null) { + return; + } + annotatedTypeMirror.replaceAnnotation(createStringAnnotation(propValue)); } } /** - * Handle the property file. Used in {@link - * ValueTransfer#visitMethodInvocation(MethodInvocationNode, TransferInput)}. + * Handle the property file. Refine the receiver type of {@link + * java.util.Properties#load(InputStream)}. Used in {@link + * ValueTransfer#visitMethodInvocation(MethodInvocationNode, TransferInput)}. When {@code + * property.load(inputStream)} is called, try to propagate {@code inputstream}'s type to {@code + * property}. * * @param node the method invocation tree * @param result the transfer result @@ -165,34 +191,37 @@ public void handle(MethodInvocationNode node, TransferResult r ExpressionTree arg0 = methodInvocationTree.getArguments().get(0); AnnotationMirror propFileAnnotation = factory.getAnnotatedType(arg0).getAnnotation(PropertyFile.class); - if (propFileAnnotation != null) { - String propFile = getValueFromPropFileAnnotation(propFileAnnotation); - if (propFile != null) { - Node receiver = node.getTarget().getReceiver(); - Receiver receiverRec = FlowExpressions.internalReprOf(factory, receiver); - propFileAnnotation = createPropertyFileAnnotation(propFile); - if (result.containsTwoStores()) { - CFStore thenStore = result.getThenStore(); - CFStore elseStore = result.getElseStore(); - thenStore.insertValue(receiverRec, propFileAnnotation); - elseStore.insertValue(receiverRec, propFileAnnotation); - } else { - CFStore regularStore = result.getRegularStore(); - regularStore.insertValue(receiverRec, propFileAnnotation); - } - } + if (propFileAnnotation == null) { + return; + } + String propFile = getValueFromPropFileAnnotation(propFileAnnotation); + if (propFile == null) { + return; + } + Node receiver = node.getTarget().getReceiver(); + Receiver receiverRec = FlowExpressions.internalReprOf(factory, receiver); + propFileAnnotation = createPropertyFileAnnotation(propFile); + if (result.containsTwoStores()) { + CFStore thenStore = result.getThenStore(); + CFStore elseStore = result.getElseStore(); + thenStore.insertValue(receiverRec, propFileAnnotation); + elseStore.insertValue(receiverRec, propFileAnnotation); + } else { + CFStore regularStore = result.getRegularStore(); + regularStore.insertValue(receiverRec, propFileAnnotation); } } } /** - * Compare two {@literal @}PropertyFile annotations. + * Return true if two {@literal @}PropertyFile annotations' element values are the same, else + * false. * * @param subAnno the sub annotation * @param superAnno the super annotation * @return true if the two annotations' values are the same */ - protected boolean comparePropFileElementValue( + protected boolean propFileElementValueEquals( AnnotationMirror subAnno, AnnotationMirror superAnno) { String subAnnoElementValue = AnnotationUtils.getElementValue(subAnno, "value", String.class, false); @@ -217,7 +246,7 @@ protected boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno return false; } else if (AnnotationUtils.areSameByClass(subAnno, PropertyFile.class) && AnnotationUtils.areSameByClass(superAnno, PropertyFile.class)) { - return comparePropFileElementValue(subAnno, superAnno); + return propFileElementValueEquals(subAnno, superAnno); } else { throw new BugInCF("We should never reach here."); } @@ -293,14 +322,15 @@ protected AnnotationMirror createStringAnnotation(String value) { } /** - * Get the first value of the {@literal @}StringVal() annotation. + * Get the value of the {@literal @}StringVal() annotation. Return null when there are more than + * one values (otherwise the PropertyFileHandler may makes the Value Checker unsound). * * @param stringVal the {@literal @}StringVal() annotation * @return the first value of the {@literal @}StringVal() annotation */ protected String getValueFromStringVal(AnnotationMirror stringVal) { List values = getStringValues(stringVal); - if (!values.isEmpty()) { + if (values.size() == 1) { return values.get(0); } else { return null; diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java index e567d695651..61c7f43da80 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java @@ -20,6 +20,7 @@ import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; +import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.common.value.qual.ArrayLen; @@ -129,7 +130,7 @@ public class ValueAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { private final ValueMethodIdentifier methods; /** The property file handler. */ - public PropertyFileHandler propertyFileHandler = null; + public final @Nullable PropertyFileHandler propertyFileHandler; /** Set of supported type qualifiers in the original value hierarchy. */ protected static final LinkedHashSet> supportedValueQualifiers = @@ -159,9 +160,11 @@ public ValueAnnotatedTypeFactory(BaseTypeChecker checker) { reportEvalWarnings = checker.hasOption(ValueChecker.REPORT_EVAL_WARNS); Range.ignoreOverflow = checker.hasOption(ValueChecker.IGNORE_RANGE_OVERFLOW); - if (checker.hasOption(ValueChecker.HANDLE_PROPERTY_FILE)) { + if (checker.hasOption(ValueChecker.HANDLE_PROPERTY_FILES)) { propertyFileHandler = new PropertyFileHandler(getProcessingEnv(), this, (ValueChecker) checker); + } else { + propertyFileHandler = null; } evaluator = new ReflectiveEvaluator(checker, this, reportEvalWarnings); diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueChecker.java b/framework/src/main/java/org/checkerframework/common/value/ValueChecker.java index 2506441e38c..6f98ffe1205 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueChecker.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueChecker.java @@ -23,7 +23,7 @@ ValueChecker.REPORT_EVAL_WARNS, ValueChecker.IGNORE_RANGE_OVERFLOW, ValueChecker.NON_NULL_STRINGS_CONCATENATION, - ValueChecker.HANDLE_PROPERTY_FILE + ValueChecker.HANDLE_PROPERTY_FILES }) public class ValueChecker extends BaseTypeChecker { /** @@ -36,7 +36,7 @@ public class ValueChecker extends BaseTypeChecker { /** Command-line option that assumes most expressions in String concatenations can be null. */ public static final String NON_NULL_STRINGS_CONCATENATION = "nonNullStringsConcatenation"; /** Command-line option to enable the property file handler. */ - public static final String HANDLE_PROPERTY_FILE = "handlePropertyFile"; + public static final String HANDLE_PROPERTY_FILES = "handlePropertyFiles"; @Override protected BaseTypeVisitor createSourceVisitor() { diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueQualifierHierarchy.java b/framework/src/main/java/org/checkerframework/common/value/ValueQualifierHierarchy.java index ed8e8ebe199..4ab586727b4 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueQualifierHierarchy.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueQualifierHierarchy.java @@ -2,7 +2,6 @@ import static org.checkerframework.common.value.PropertyFileHandler.inPropertyFileQualifierHierarchy; -import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.List; import java.util.TreeSet; @@ -376,14 +375,14 @@ public boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno) { if (inPropertyFileQualifierHierarchy(subAnno) && inPropertyFileQualifierHierarchy(superAnno)) { return atypeFactory.propertyFileHandler.isSubtype(subAnno, superAnno); - } else if ((inValueQualifierHierarchy(subAnno) + } else if ((!inPropertyFileQualifierHierarchy(subAnno) && inPropertyFileQualifierHierarchy(superAnno)) || (inPropertyFileQualifierHierarchy(subAnno) - && inValueQualifierHierarchy(superAnno))) { - // Fall through when two are in the different type hierarchy. + && !inPropertyFileQualifierHierarchy(superAnno))) { return false; } } + // Fall through when two are in the value hierarchy. subAnno = atypeFactory.convertSpecialIntRangeToStandardIntRange(subAnno); superAnno = atypeFactory.convertSpecialIntRangeToStandardIntRange(superAnno); String subQual = AnnotationUtils.annotationName(subAnno); @@ -489,20 +488,4 @@ && inValueQualifierHierarchy(superAnno))) { return false; } } - - /** - * Return true if the target annotation is in the original value qualifier hierarchy. - * - * @param anno the annotation to check - * @return true if the target annotation is in the original value qualifier hierarchy - */ - private boolean inValueQualifierHierarchy(AnnotationMirror anno) { - for (Class each : - ValueAnnotatedTypeFactory.supportedValueQualifiers) { - if (AnnotationUtils.areSameByClass(anno, each)) { - return true; - } - } - return false; - } } diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueTreeAnnotator.java b/framework/src/main/java/org/checkerframework/common/value/ValueTreeAnnotator.java index 26ace082287..19ead8054b0 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueTreeAnnotator.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueTreeAnnotator.java @@ -388,7 +388,6 @@ private Range getRangeForMathMinMax(MethodInvocationTree tree) { @Override public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { - if (atypeFactory.propertyFileHandler != null) { atypeFactory.propertyFileHandler.handle(tree, type); } diff --git a/framework/src/test/java/tests/ValueHandlePropertyFileTest.java b/framework/src/test/java/tests/ValueHandlePropertyFileTest.java index 4e967d0c893..0a414ccbb2a 100644 --- a/framework/src/test/java/tests/ValueHandlePropertyFileTest.java +++ b/framework/src/test/java/tests/ValueHandlePropertyFileTest.java @@ -18,7 +18,7 @@ public ValueHandlePropertyFileTest(List testFiles) { "-Anomsgtext", "-Astubs=statically-executable.astub", "-A" + ValueChecker.REPORT_EVAL_WARNS, - "-A" + ValueChecker.HANDLE_PROPERTY_FILE); + "-A" + ValueChecker.HANDLE_PROPERTY_FILES); } @Parameters From ca0886fcf123cf91b007610c93574940eaf5370c Mon Sep 17 00:00:00 2001 From: xingweitian Date: Wed, 3 Jun 2020 21:20:15 -0400 Subject: [PATCH 7/8] Resolve comments. --- .../org/checkerframework/common/value/PropertyFileHandler.java | 1 - 1 file changed, 1 deletion(-) diff --git a/framework/src/main/java/org/checkerframework/common/value/PropertyFileHandler.java b/framework/src/main/java/org/checkerframework/common/value/PropertyFileHandler.java index ef1a96f0594..fa468fd72ff 100644 --- a/framework/src/main/java/org/checkerframework/common/value/PropertyFileHandler.java +++ b/framework/src/main/java/org/checkerframework/common/value/PropertyFileHandler.java @@ -294,7 +294,6 @@ protected String readValueFromPropertyFile(String propFile, String key, String d } catch (Exception e) { checker.message( Kind.WARNING, "Exception in PropertyFileHandler.readPropertyFromFile: " + e); - e.printStackTrace(); } return res; } From 6b942975f8ba3798a0ad4722f75c50be0bf26f19 Mon Sep 17 00:00:00 2001 From: Weitian Xing Date: Tue, 4 Aug 2020 22:57:10 -0400 Subject: [PATCH 8/8] Report warning if key not exist in the properties files. Also add default value to @StringVal. --- .../common/value/PropertyFileHandler.java | 44 +++++++++++++------ .../common/value/messages.properties | 1 + .../tests/ValueHandlePropertyFileTest.java | 1 - .../PropertyFileRead.java | 3 +- 4 files changed, 34 insertions(+), 15 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/value/PropertyFileHandler.java b/framework/src/main/java/org/checkerframework/common/value/PropertyFileHandler.java index fa468fd72ff..30bdba93f85 100644 --- a/framework/src/main/java/org/checkerframework/common/value/PropertyFileHandler.java +++ b/framework/src/main/java/org/checkerframework/common/value/PropertyFileHandler.java @@ -7,9 +7,10 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; -import java.util.Collections; +import java.util.Arrays; import java.util.List; import java.util.Properties; +import java.util.Set; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; @@ -30,6 +31,7 @@ import org.checkerframework.javacutil.AnnotationBuilder; import org.checkerframework.javacutil.AnnotationUtils; import org.checkerframework.javacutil.BugInCF; +import org.checkerframework.javacutil.Pair; import org.checkerframework.javacutil.TreeUtils; /** @@ -143,11 +145,12 @@ public void handle(MethodInvocationTree node, AnnotatedTypeMirror annotatedTypeM return; } String propKey = getValueFromStringVal(stringAnnotation); - String propValue = readValueFromPropertyFile(propFile, propKey, null); - if (propValue == null) { + Pair propValues = + readValueFromPropertyFile(propFile, propKey, null, node); + if (propValues == null) { return; } - annotatedTypeMirror.replaceAnnotation(createStringAnnotation(propValue)); + annotatedTypeMirror.replaceAnnotation(createStringAnnotation(propValues.first)); } else if (TreeUtils.isMethodInvocation(node, getPropertyWithDefaultValue, env)) { AnnotationMirror propFileAnnotation = factory.getReceiverType(node).getAnnotation(PropertyFile.class); @@ -167,11 +170,17 @@ public void handle(MethodInvocationTree node, AnnotatedTypeMirror annotatedTypeM } else { defaultValue = null; } - String propValue = readValueFromPropertyFile(propFile, propKey, defaultValue); - if (propValue == null) { + Pair propValues = + readValueFromPropertyFile(propFile, propKey, defaultValue, node); + if (propValues == null) { return; } - annotatedTypeMirror.replaceAnnotation(createStringAnnotation(propValue)); + if (propValues.first.equals(propValues.second)) { + annotatedTypeMirror.replaceAnnotation(createStringAnnotation(propValues.first)); + } else { + annotatedTypeMirror.replaceAnnotation( + createStringAnnotation(propValues.first, propValues.second)); + } } } @@ -258,9 +267,11 @@ protected boolean isSubtype(AnnotationMirror subAnno, AnnotationMirror superAnno * @param propFile the property file to open * @param key the key to find in the property file * @param defaultValue the default value of that key + * @param node the method invocation tree used for reporting warning * @return the value of the key in the property file */ - protected String readValueFromPropertyFile(String propFile, String key, String defaultValue) { + protected Pair readValueFromPropertyFile( + String propFile, String key, String defaultValue, MethodInvocationTree node) { String res = null; try { Properties prop = new Properties(); @@ -286,6 +297,13 @@ protected String readValueFromPropertyFile(String propFile, String key, String d return null; } prop.load(in); + Set keyNames = prop.stringPropertyNames(); + if (!keyNames.contains(key)) { + checker.reportWarning(node, "key.not.exist.in.properties.file", key, propFile); + if (defaultValue == null) { + return null; + } + } if (defaultValue == null) { res = prop.getProperty(key); } else { @@ -295,7 +313,7 @@ protected String readValueFromPropertyFile(String propFile, String key, String d checker.message( Kind.WARNING, "Exception in PropertyFileHandler.readPropertyFromFile: " + e); } - return res; + return Pair.of(res, defaultValue); } /** @@ -311,13 +329,13 @@ protected AnnotationMirror createPropertyFileAnnotation(String value) { } /** - * Create a {@literal @}StringVal(value) annotation. + * Create a {@literal @}StringVal(values) annotation. * - * @param value the value to set in the created annotation + * @param values the values to set in the created annotation * @return the created annotation */ - protected AnnotationMirror createStringAnnotation(String value) { - return factory.createStringAnnotation(Collections.singletonList(value)); + protected AnnotationMirror createStringAnnotation(String... values) { + return factory.createStringAnnotation(Arrays.asList(values)); } /** diff --git a/framework/src/main/java/org/checkerframework/common/value/messages.properties b/framework/src/main/java/org/checkerframework/common/value/messages.properties index b4895354afc..561953b38ab 100644 --- a/framework/src/main/java/org/checkerframework/common/value/messages.properties +++ b/framework/src/main/java/org/checkerframework/common/value/messages.properties @@ -16,3 +16,4 @@ from.greater.than.to=The "from" value must be less than or equal to the "to" val negative.arraylen=Negative array lengths are not allowed.%nfound: %s class.convert.failed=Cannot convert annotation %s to class %s" annotation.intrange.on.noninteger=@IntRange can only be used on integral types. +key.not.exist.in.properties.file=Key "%s" does not exist in the properties file: %s diff --git a/framework/src/test/java/tests/ValueHandlePropertyFileTest.java b/framework/src/test/java/tests/ValueHandlePropertyFileTest.java index 0a414ccbb2a..6db9d85e962 100644 --- a/framework/src/test/java/tests/ValueHandlePropertyFileTest.java +++ b/framework/src/test/java/tests/ValueHandlePropertyFileTest.java @@ -16,7 +16,6 @@ public ValueHandlePropertyFileTest(List testFiles) { org.checkerframework.common.value.ValueChecker.class, "value", "-Anomsgtext", - "-Astubs=statically-executable.astub", "-A" + ValueChecker.REPORT_EVAL_WARNS, "-A" + ValueChecker.HANDLE_PROPERTY_FILES); } diff --git a/framework/tests/value-handle-property-file/PropertyFileRead.java b/framework/tests/value-handle-property-file/PropertyFileRead.java index 0acf19cb549..8fca4de158e 100644 --- a/framework/tests/value-handle-property-file/PropertyFileRead.java +++ b/framework/tests/value-handle-property-file/PropertyFileRead.java @@ -18,13 +18,14 @@ void b() throws IOException { Properties prop = new Properties(); InputStream inputStream = getClass().getClassLoader().getResourceAsStream(propFile); prop.load(inputStream); - @StringVal("localhost") String host = prop.getProperty("HOST", "127.0.0.1"); + @StringVal({"localhost", "127.0.0.1"}) String host = prop.getProperty("HOST", "127.0.0.1"); } void c() throws IOException { Properties prop = new Properties(); InputStream inputStream = getClass().getClassLoader().getResourceAsStream(propFile); prop.load(inputStream); + // :: warning: (key.not.exist.in.properties.file) @StringVal("default value") String host = prop.getProperty("NOSUCHKEY", "default value"); }