package org.checkerframework.checker.regex;
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.Tree;
import com.sun.source.tree.Tree.Kind;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.VariableElement;
import org.checkerframework.checker.regex.qual.Regex;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.common.basetype.BaseTypeVisitor;
import org.checkerframework.framework.source.Result;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedPrimitiveType;
import org.checkerframework.javacutil.TreeUtils;
/**
* A type-checking visitor for the Regex type system.
*
* <p>This visitor does the following:
*
* <ol>
* <li value="1">Allows any String to be passed to Pattern.compile if the Pattern.LITERAL flag is
* passed.
* <li value="2">Checks compound String concatenation to ensure correct usage of Regex Strings.
* <li value="3">Checks calls to {@code MatchResult.start}, {@code MatchResult.end} and {@code
* MatchResult.group} to ensure that a valid group number is passed.
* </ol>
*
* @see RegexChecker
*/
public class RegexVisitor extends BaseTypeVisitor<RegexAnnotatedTypeFactory> {
private final ExecutableElement matchResultEnd;
private final ExecutableElement matchResultGroup;
private final ExecutableElement matchResultStart;
private final ExecutableElement patternCompile;
private final VariableElement patternLiteral;
public RegexVisitor(BaseTypeChecker checker) {
super(checker);
ProcessingEnvironment env = checker.getProcessingEnvironment();
this.matchResultEnd =
TreeUtils.getMethod(java.util.regex.MatchResult.class.getName(), "end", 1, env);
this.matchResultGroup =
TreeUtils.getMethod(java.util.regex.MatchResult.class.getName(), "group", 1, env);
this.matchResultStart =
TreeUtils.getMethod(java.util.regex.MatchResult.class.getName(), "start", 1, env);
this.patternCompile =
TreeUtils.getMethod(java.util.regex.Pattern.class.getName(), "compile", 2, env);
this.patternLiteral =
TreeUtils.getField(java.util.regex.Pattern.class.getName(), "LITERAL", env);
}
/**
* Case 1: Don't require a Regex annotation on the String argument to Pattern.compile if the
* Pattern.LITERAL flag is passed.
*/
@Override
public Void visitMethodInvocation(MethodInvocationTree node, Void p) {
ProcessingEnvironment env = checker.getProcessingEnvironment();
if (TreeUtils.isMethodInvocation(node, patternCompile, env)) {
ExpressionTree flagParam = node.getArguments().get(1);
if (flagParam.getKind() == Kind.MEMBER_SELECT) {
MemberSelectTree memSelect = (MemberSelectTree) flagParam;
if (TreeUtils.isSpecificFieldAccess(memSelect, patternLiteral)) {
// This is a call to Pattern.compile with the Pattern.LITERAL
// flag so the first parameter doesn't need to be a
// @Regex String. Don't call the super method to skip checking
// if the first parameter is a @Regex String, but make sure to
// still recurse on all of the different parts of the method call.
Void r = scan(node.getTypeArguments(), p);
r = reduce(scan(node.getMethodSelect(), p), r);
r = reduce(scan(node.getArguments(), p), r);
return r;
}
}
} else if (TreeUtils.isMethodInvocation(node, matchResultEnd, env)
|| TreeUtils.isMethodInvocation(node, matchResultGroup, env)
|| TreeUtils.isMethodInvocation(node, matchResultStart, env)) {
/**
* Case 3: Checks calls to {@code MatchResult.start}, {@code MatchResult.end} and {@code
* MatchResult.group} to ensure that a valid group number is passed.
*/
ExpressionTree group = node.getArguments().get(0);
if (group.getKind() == Kind.INT_LITERAL) {
LiteralTree literal = (LiteralTree) group;
int paramGroups = (Integer) literal.getValue();
ExpressionTree receiver = TreeUtils.getReceiverTree(node);
int annoGroups = 0;
AnnotatedTypeMirror receiverType = atypeFactory.getAnnotatedType(receiver);
if (receiverType.hasAnnotation(Regex.class)) {
annoGroups =
atypeFactory.getGroupCount(receiverType.getAnnotation(Regex.class));
}
if (paramGroups > annoGroups) {
checker.report(
Result.failure(
"group.count.invalid", paramGroups, annoGroups, receiver),
group);
}
} else {
checker.report(Result.warning("group.count.unknown"), group);
}
}
return super.visitMethodInvocation(node, p);
}
/** Case 2: Check String compound concatenation for valid Regex use. */
// TODO: Remove this. This should be handled by flow.
/*@Override
public Void visitCompoundAssignment(CompoundAssignmentTree node, Void p) {
// Default behavior from superclass
}*/
@Override
public boolean isValidUse(
AnnotatedDeclaredType declarationType, AnnotatedDeclaredType useType, Tree tree) {
// TODO: only allow Regex and PolyRegex annotations on types in legalReferenceTypes.
// This is pending an implementation of AnnotatedTypeMirror.getExplicitAnnotations
// that supports local variables, array types and parameterized types.
/*// Only allow annotations on subtypes of the types in legalReferenceTypes.
if (!useType.getExplicitAnnotations().isEmpty()) {
Types typeUtils = env.getTypeUtils();
for (TypeMirror type : legalReferenceTypes) {
if (typeUtils.isSubtype(declarationType.getUnderlyingType(), type)) {
return true;
}
}
return false;
}*/
return super.isValidUse(declarationType, useType, tree);
}
@Override
public boolean isValidUse(AnnotatedPrimitiveType type, Tree tree) {
// TODO: only allow Regex and PolyRegex annotations on chars.
// This is pending an implementation of AnnotatedTypeMirror.getExplicitAnnotations
// that supports local variables and array types.
/*// Only allow annotations on char.
if (!type.getExplicitAnnotations().isEmpty()) {
return type.getKind() == TypeKind.CHAR;
}*/
return super.isValidUse(type, tree);
}
}