blob: 4533e54f7ffd600d8eb6d9da5711270b1e969604 [file] [log] [blame]
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.Kind;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.checker.regex.qual.Regex;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.common.basetype.BaseTypeVisitor;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
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;
/** Reference types that may be annotated with @Regex. */
protected TypeMirror[] legalReferenceTypes;
/**
* Create a RegexVisitor.
*
* @param checker the associated RegexChecker
*/
public RegexVisitor(BaseTypeChecker checker) {
super(checker);
ProcessingEnvironment env = checker.getProcessingEnvironment();
this.matchResultEnd = TreeUtils.getMethod("java.util.regex.MatchResult", "end", 1, env);
this.matchResultGroup = TreeUtils.getMethod("java.util.regex.MatchResult", "group", 1, env);
this.matchResultStart = TreeUtils.getMethod("java.util.regex.MatchResult", "start", 1, env);
this.patternCompile = TreeUtils.getMethod("java.util.regex.Pattern", "compile", 2, env);
this.patternLiteral = TreeUtils.getField("java.util.regex.Pattern", "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);
if (receiver == null) {
// When checking implementations of java.util.regex.MatcherResult, calls to group (and
// other methods) don't have a receiver tree. So, just do the regular checking. Verifying
// an implemenation of a subclass of MatcherResult is out of the scope of this checker.
return super.visitMethodInvocation(node, p);
}
int annoGroups = 0;
AnnotatedTypeMirror receiverType = atypeFactory.getAnnotatedType(receiver);
if (receiverType != null && receiverType.hasAnnotation(Regex.class)) {
annoGroups = atypeFactory.getGroupCount(receiverType.getAnnotation(Regex.class));
}
if (paramGroups > annoGroups) {
checker.reportError(group, "group.count", paramGroups, annoGroups, receiver);
}
} else {
checker.reportWarning(group, "group.count.unknown");
}
}
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
}
*/
}