From 69d703d6a2be7470c696ab59c43798a2842ab880 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Fri, 11 Oct 2024 11:04:23 -0700 Subject: [PATCH] More precise analysis of Signature string manipulation --- .../SignatureAnnotatedTypeFactory.java | 25 ++++++++++--- checker/tests/signature/Concatenation.java | 8 ++++ .../value/ValueAnnotatedTypeFactory.java | 2 +- .../javacutil/TypeKindUtils.java | 19 ++++++++++ .../javacutil/TypesUtils.java | 37 ++++++++++--------- 5 files changed, 68 insertions(+), 23 deletions(-) create mode 100644 checker/tests/signature/Concatenation.java diff --git a/checker/src/main/java/org/checkerframework/checker/signature/SignatureAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/signature/SignatureAnnotatedTypeFactory.java index c608b0d2a53..0d0dd126e2a 100644 --- a/checker/src/main/java/org/checkerframework/checker/signature/SignatureAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/signature/SignatureAnnotatedTypeFactory.java @@ -84,6 +84,10 @@ public class SignatureAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { protected final AnnotationMirror PRIMITIVE_TYPE = AnnotationBuilder.fromClass(elements, PrimitiveType.class); + /** The {@literal @}{@link Identifier} annotation. */ + protected final AnnotationMirror IDENTIFIER = + AnnotationBuilder.fromClass(elements, Identifier.class); + /** The {@link String#replace(char, char)} method. */ private final ExecutableElement replaceCharChar = TreeUtils.getMethod("java.lang.String", "replace", processingEnv, "char", "char"); @@ -209,9 +213,20 @@ public SignatureTreeAnnotator(AnnotatedTypeFactory atypeFactory) { @Override public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { + if (TreeUtils.isStringConcatenation(tree)) { - // This could be made more precise. - type.replaceAnnotation(SIGNATURE_UNKNOWN); + AnnotatedTypeMirror lType = getAnnotatedType(tree.getLeftOperand()); + AnnotatedTypeMirror rType = getAnnotatedType(tree.getRightOperand()); + + // An identifier can end, but not start, with digits + if (lType.getPrimaryAnnotation(Identifier.class) != null + && (rType.getPrimaryAnnotation(Identifier.class) != null + || TypesUtils.isIntegralNumericOrBoxed(rType.getUnderlyingType()))) { + type.replaceAnnotation(IDENTIFIER); + } else { + // This could be made more precise. + type.replaceAnnotation(SIGNATURE_UNKNOWN); + } } return null; // super.visitBinary(tree, type); } @@ -231,10 +246,10 @@ public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMi * *

      * {@literal @}InternalForm String internalForm = binaryName.replace('.', '/');
-     * {@literal @}BinaryName String binaryName = internalForm.replace('/', '.');
+     * {@literal @}DotSeparatedIdentifiers String dsi = internalForm.replace('/', '.');
      * 
* - * Class.getName and Class.getCanonicalName(): Cwhen called on a primitive type ,the return a + * Class.getName and Class.getCanonicalName(): when called on a primitive type, they return a * {@link PrimitiveType}. When called on a non-array, non-nested, non-primitive type, they * return a {@link BinaryName}: * @@ -276,7 +291,7 @@ public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type.replaceAnnotation(INTERNAL_FORM); } else if ((oldChar == '/' && newChar == '.') && receiverType.getPrimaryAnnotation(InternalForm.class) != null) { - type.replaceAnnotation(BINARY_NAME); + type.replaceAnnotation(DOT_SEPARATED_IDENTIFIERS); } } else { boolean isClassGetName = TreeUtils.isMethodInvocation(tree, classGetName, processingEnv); diff --git a/checker/tests/signature/Concatenation.java b/checker/tests/signature/Concatenation.java new file mode 100644 index 00000000000..a8e673b1a6e --- /dev/null +++ b/checker/tests/signature/Concatenation.java @@ -0,0 +1,8 @@ +import org.checkerframework.checker.signature.qual.*; + +public class Concatenation { + + @Identifier String m(@Identifier String s, int i) { + return s + i; + } +} 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 4a3a4af2cf1..094ded83315 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java @@ -641,7 +641,7 @@ public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror mirror) { /*package-private*/ AnnotationMirror convertSpecialIntRangeToStandardIntRange( AnnotationMirror anm, TypeKind primitiveKind) { long max = Long.MAX_VALUE; - if (TypesUtils.isIntegralPrimitive(primitiveKind)) { + if (TypeKindUtils.isIntegral(primitiveKind)) { Range maxRange = Range.create(primitiveKind); max = maxRange.to; } diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TypeKindUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TypeKindUtils.java index 12d70478844..c53b6d44d98 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TypeKindUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TypeKindUtils.java @@ -32,6 +32,25 @@ public static boolean isIntegral(TypeKind typeKind) { } } + /** + * Return true if the argument is one of INT, SHORT, BYTE, LONG. + * + * @param typeKind the TypeKind to inspect + * @return true if typeKind is a primitive integral type kind, excluding CHAR which does not print + * as an integer + */ + public static boolean isIntegralNumeric(TypeKind typeKind) { + switch (typeKind) { + case INT: + case SHORT: + case BYTE: + case LONG: + return true; + default: + return false; + } + } + /** * Return true if the argument is one of FLOAT, DOUBLE. * diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java index ccdc560e362..bd91c404e60 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java @@ -542,37 +542,40 @@ public static boolean isNumericBoxed(TypeMirror type) { * @return whether the argument is an integral primitive type */ public static boolean isIntegralPrimitive(TypeMirror type) { - return isIntegralPrimitive(type.getKind()); + return TypeKindUtils.isIntegral(type.getKind()); + } + + /** + * Return true if the argument TypeMirror is a (possibly boxed) integral type. + * + * @param type the type to inspect + * @return true if type is an integral type + */ + public static boolean isIntegralPrimitiveOrBoxed(TypeMirror type) { + TypeKind kind = TypeKindUtils.primitiveOrBoxedToTypeKind(type); + return kind != null && TypeKindUtils.isIntegral(kind); } /** * Returns true iff the argument is an integral primitive type. * - * @param typeKind a type kind + * @param type a type * @return whether the argument is an integral primitive type */ - public static boolean isIntegralPrimitive(TypeKind typeKind) { - switch (typeKind) { - case BYTE: - case CHAR: - case INT: - case LONG: - case SHORT: - return true; - default: - return false; - } + public static boolean isIntegralNumericPrimitive(TypeMirror type) { + return TypeKindUtils.isIntegralNumeric(type.getKind()); } /** - * Return true if the argument TypeMirror is a (possibly boxed) integral type. + * Return true if the argument TypeMirror is a (possibly boxed) integral type, excluding char and + * Character which do not print as numbers. * * @param type the type to inspect - * @return true if type is an integral type + * @return true if type is an integral numeric type */ - public static boolean isIntegralPrimitiveOrBoxed(TypeMirror type) { + public static boolean isIntegralNumericOrBoxed(TypeMirror type) { TypeKind kind = TypeKindUtils.primitiveOrBoxedToTypeKind(type); - return kind != null && TypeKindUtils.isIntegral(kind); + return kind != null && TypeKindUtils.isIntegralNumeric(kind); } /**