| 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 |
| } |
| */ |
| |
| } |