blob: 3d81287f1724cbb2f06e86e5e24a0e0478ed81de [file] [log] [blame]
package org.checkerframework.checker.signature;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.LiteralTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.PrimitiveTypeTree;
import com.sun.source.tree.Tree;
import java.lang.annotation.Annotation;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.checker.signature.qual.ArrayWithoutPackage;
import org.checkerframework.checker.signature.qual.BinaryName;
import org.checkerframework.checker.signature.qual.BinaryNameOrPrimitiveType;
import org.checkerframework.checker.signature.qual.BinaryNameWithoutPackage;
import org.checkerframework.checker.signature.qual.CanonicalName;
import org.checkerframework.checker.signature.qual.CanonicalNameAndBinaryName;
import org.checkerframework.checker.signature.qual.ClassGetName;
import org.checkerframework.checker.signature.qual.ClassGetSimpleName;
import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers;
import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiersOrPrimitiveType;
import org.checkerframework.checker.signature.qual.FieldDescriptor;
import org.checkerframework.checker.signature.qual.FieldDescriptorForPrimitive;
import org.checkerframework.checker.signature.qual.FieldDescriptorWithoutPackage;
import org.checkerframework.checker.signature.qual.FqBinaryName;
import org.checkerframework.checker.signature.qual.FullyQualifiedName;
import org.checkerframework.checker.signature.qual.Identifier;
import org.checkerframework.checker.signature.qual.IdentifierOrPrimitiveType;
import org.checkerframework.checker.signature.qual.InternalForm;
import org.checkerframework.checker.signature.qual.PrimitiveType;
import org.checkerframework.checker.signature.qual.SignatureBottom;
import org.checkerframework.checker.signature.qual.SignatureUnknown;
import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.LiteralTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
import org.checkerframework.javacutil.AnnotationBuilder;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;
import org.plumelib.reflection.SignatureRegexes;
// TODO: Does not yet handle method signature annotations, such as
// @MethodDescriptor.
/** Accounts for the effects of certain calls to String.replace. */
public class SignatureAnnotatedTypeFactory extends BaseAnnotatedTypeFactory {
/** The {@literal @}{@link SignatureUnknown} annotation. */
protected final AnnotationMirror SIGNATURE_UNKNOWN =
AnnotationBuilder.fromClass(elements, SignatureUnknown.class);
/** The {@literal @}{@link BinaryName} annotation. */
protected final AnnotationMirror BINARY_NAME =
AnnotationBuilder.fromClass(elements, BinaryName.class);
/** The {@literal @}{@link InternalForm} annotation. */
protected final AnnotationMirror INTERNAL_FORM =
AnnotationBuilder.fromClass(elements, InternalForm.class);
/** The {@literal @}{@link DotSeparatedIdentifiers} annotation. */
protected final AnnotationMirror DOT_SEPARATED_IDENTIFIERS =
AnnotationBuilder.fromClass(elements, DotSeparatedIdentifiers.class);
/** The {@literal @}{@link CanonicalName} annotation. */
protected final AnnotationMirror CANONICAL_NAME =
AnnotationBuilder.fromClass(elements, CanonicalName.class);
/** The {@literal @}{@link CanonicalNameAndBinaryName} annotation. */
protected final AnnotationMirror CANONICAL_NAME_AND_BINARY_NAME =
AnnotationBuilder.fromClass(elements, CanonicalNameAndBinaryName.class);
/** The {@literal @}{@link PrimitiveType} annotation. */
protected final AnnotationMirror PRIMITIVE_TYPE =
AnnotationBuilder.fromClass(elements, PrimitiveType.class);
/** The {@link String#replace(char, char)} method. */
private final ExecutableElement replaceCharChar =
TreeUtils.getMethod("java.lang.String", "replace", processingEnv, "char", "char");
/** The {@link String#replace(CharSequence, CharSequence)} method. */
private final ExecutableElement replaceCharSequenceCharSequence =
TreeUtils.getMethod(
"java.lang.String",
"replace",
processingEnv,
"java.lang.CharSequence",
"java.lang.CharSequence");
/** The {@link Class#getName()} method. */
private final ExecutableElement classGetName =
TreeUtils.getMethod("java.lang.Class", "getName", processingEnv);
/** The {@link Class#getCanonicalName()} method. */
private final ExecutableElement classGetCanonicalName =
TreeUtils.getMethod(java.lang.Class.class, "getCanonicalName", processingEnv);
/**
* Creates a SignatureAnnotatedTypeFactory.
*
* @param checker the type-checker assocated with this type factory
*/
public SignatureAnnotatedTypeFactory(BaseTypeChecker checker) {
super(checker);
this.postInit();
}
@Override
protected Set<Class<? extends Annotation>> createSupportedTypeQualifiers() {
return getBundledTypeQualifiers(SignatureUnknown.class, SignatureBottom.class);
}
@Override
public TreeAnnotator createTreeAnnotator() {
// It is slightly inefficient that super also adds a LiteralTreeAnnotator, but it seems
// better than hard-coding the behavior of super here.
return new ListTreeAnnotator(
signatureLiteralTreeAnnotator(this),
new SignatureTreeAnnotator(this),
super.createTreeAnnotator());
}
/**
* Create a LiteralTreeAnnotator for the Signature Checker.
*
* @param atypeFactory the type factory
* @return a LiteralTreeAnnotator for the Signature Checker
*/
private LiteralTreeAnnotator signatureLiteralTreeAnnotator(AnnotatedTypeFactory atypeFactory) {
LiteralTreeAnnotator result = new LiteralTreeAnnotator(atypeFactory);
result.addStandardLiteralQualifiers();
// The below code achieves the same effect as writing a meta-annotation
// @QualifierForLiterals(stringPatterns = "...")
// on each type qualifier definition. Annotation elements cannot be computations (not even
// string concatenations of literal strings) and cannot be not references to compile-time
// constants such as effectively-final fields. So every `stringPatterns = "..."` would have
// to be a literal string, which would be verbose ard hard to maintain.
result.addStringPattern(
SignatureRegexes.ArrayWithoutPackageRegex,
AnnotationBuilder.fromClass(elements, ArrayWithoutPackage.class));
result.addStringPattern(
SignatureRegexes.BinaryNameRegex, AnnotationBuilder.fromClass(elements, BinaryName.class));
result.addStringPattern(
SignatureRegexes.BinaryNameOrPrimitiveTypeRegex,
AnnotationBuilder.fromClass(elements, BinaryNameOrPrimitiveType.class));
result.addStringPattern(
SignatureRegexes.BinaryNameWithoutPackageRegex,
AnnotationBuilder.fromClass(elements, BinaryNameWithoutPackage.class));
result.addStringPattern(
SignatureRegexes.ClassGetNameRegex,
AnnotationBuilder.fromClass(elements, ClassGetName.class));
result.addStringPattern(
SignatureRegexes.ClassGetSimpleNameRegex,
AnnotationBuilder.fromClass(elements, ClassGetSimpleName.class));
result.addStringPattern(
SignatureRegexes.DotSeparatedIdentifiersRegex,
AnnotationBuilder.fromClass(elements, DotSeparatedIdentifiers.class));
result.addStringPattern(
SignatureRegexes.DotSeparatedIdentifiersOrPrimitiveTypeRegex,
AnnotationBuilder.fromClass(elements, DotSeparatedIdentifiersOrPrimitiveType.class));
result.addStringPattern(
SignatureRegexes.FieldDescriptorRegex,
AnnotationBuilder.fromClass(elements, FieldDescriptor.class));
result.addStringPattern(
SignatureRegexes.FieldDescriptorForPrimitiveRegex,
AnnotationBuilder.fromClass(elements, FieldDescriptorForPrimitive.class));
result.addStringPattern(
SignatureRegexes.FieldDescriptorWithoutPackageRegex,
AnnotationBuilder.fromClass(elements, FieldDescriptorWithoutPackage.class));
result.addStringPattern(
SignatureRegexes.FqBinaryNameRegex,
AnnotationBuilder.fromClass(elements, FqBinaryName.class));
result.addStringPattern(
SignatureRegexes.FullyQualifiedNameRegex,
AnnotationBuilder.fromClass(elements, FullyQualifiedName.class));
result.addStringPattern(
SignatureRegexes.IdentifierRegex, AnnotationBuilder.fromClass(elements, Identifier.class));
result.addStringPattern(
SignatureRegexes.IdentifierOrPrimitiveTypeRegex,
AnnotationBuilder.fromClass(elements, IdentifierOrPrimitiveType.class));
result.addStringPattern(
SignatureRegexes.InternalFormRegex,
AnnotationBuilder.fromClass(elements, InternalForm.class));
result.addStringPattern(
SignatureRegexes.PrimitiveTypeRegex,
AnnotationBuilder.fromClass(elements, PrimitiveType.class));
return result;
}
private class SignatureTreeAnnotator extends TreeAnnotator {
public SignatureTreeAnnotator(AnnotatedTypeFactory atypeFactory) {
super(atypeFactory);
}
@Override
public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) {
if (TreeUtils.isStringConcatenation(tree)) {
type.removeAnnotationInHierarchy(SIGNATURE_UNKNOWN);
// This could be made more precise.
type.addAnnotation(SignatureUnknown.class);
}
return null; // super.visitBinary(tree, type);
}
@Override
public Void visitCompoundAssignment(CompoundAssignmentTree node, AnnotatedTypeMirror type) {
if (TreeUtils.isStringCompoundConcatenation(node)) {
type.removeAnnotationInHierarchy(SIGNATURE_UNKNOWN);
// This could be made more precise.
type.addAnnotation(SignatureUnknown.class);
}
return null; // super.visitCompoundAssignment(node, type);
}
/**
* String.replace, when called with specific constant arguments, converts between internal form
* and binary name:
*
* <pre><code>
* {@literal @}InternalForm String internalForm = binaryName.replace('.', '/');
* {@literal @}BinaryName String binaryName = internalForm.replace('/', '.');
* </code></pre>
*
* Class.getName and Class.getCanonicalName(): Cwhen called on a primitive type ,the return a
* {@link PrimitiveType}. When called on a non-array, non-nested, non-primitive type, they
* return a {@link BinaryName}:
*
* <pre><code>
* {@literal @}BinaryName String binaryName = MyClass.class.getName();
* </code></pre>
*/
@Override
public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) {
if (TreeUtils.isMethodInvocation(tree, replaceCharChar, processingEnv)
|| TreeUtils.isMethodInvocation(tree, replaceCharSequenceCharSequence, processingEnv)) {
char oldChar = ' '; // initial dummy value
char newChar = ' '; // initial dummy value
if (TreeUtils.isMethodInvocation(tree, replaceCharChar, processingEnv)) {
ExpressionTree arg0 = tree.getArguments().get(0);
ExpressionTree arg1 = tree.getArguments().get(1);
if (arg0.getKind() == Tree.Kind.CHAR_LITERAL
&& arg1.getKind() == Tree.Kind.CHAR_LITERAL) {
oldChar = (char) ((LiteralTree) arg0).getValue();
newChar = (char) ((LiteralTree) arg1).getValue();
}
} else {
ExpressionTree arg0 = tree.getArguments().get(0);
ExpressionTree arg1 = tree.getArguments().get(1);
if (arg0.getKind() == Tree.Kind.STRING_LITERAL
&& arg1.getKind() == Tree.Kind.STRING_LITERAL) {
String const0 = (String) ((LiteralTree) arg0).getValue();
String const1 = (String) ((LiteralTree) arg1).getValue();
if (const0.length() == 1 && const1.length() == 1) {
oldChar = const0.charAt(0);
newChar = const1.charAt(0);
}
}
}
ExpressionTree receiver = TreeUtils.getReceiverTree(tree);
final AnnotatedTypeMirror receiverType = getAnnotatedType(receiver);
if ((oldChar == '.' && newChar == '/')
&& receiverType.getAnnotation(BinaryName.class) != null) {
type.replaceAnnotation(INTERNAL_FORM);
} else if ((oldChar == '/' && newChar == '.')
&& receiverType.getAnnotation(InternalForm.class) != null) {
type.replaceAnnotation(BINARY_NAME);
}
} else {
boolean isClassGetName = TreeUtils.isMethodInvocation(tree, classGetName, processingEnv);
boolean isClassGetCanonicalName =
TreeUtils.isMethodInvocation(tree, classGetCanonicalName, processingEnv);
if (isClassGetName || isClassGetCanonicalName) {
ExpressionTree receiver = TreeUtils.getReceiverTree(tree);
if (TreeUtils.isClassLiteral(receiver)) {
ExpressionTree classExpr = ((MemberSelectTree) receiver).getExpression();
if (classExpr.getKind() == Tree.Kind.PRIMITIVE_TYPE) {
if (((PrimitiveTypeTree) classExpr).getPrimitiveTypeKind() == TypeKind.VOID) {
// do nothing
} else {
type.replaceAnnotation(PRIMITIVE_TYPE);
}
} else {
// Binary name if non-array, non-primitive, non-nested.
TypeMirror literalType = TreeUtils.typeOf(classExpr);
if (literalType.getKind() == TypeKind.DECLARED) {
TypeElement typeElt = TypesUtils.getTypeElement(literalType);
Element enclosing = typeElt.getEnclosingElement();
if (enclosing == null || enclosing.getKind() == ElementKind.PACKAGE) {
type.replaceAnnotation(
isClassGetName ? DOT_SEPARATED_IDENTIFIERS : CANONICAL_NAME_AND_BINARY_NAME);
}
}
}
}
}
}
return super.visitMethodInvocation(tree, type);
}
}
}