| package org.checkerframework.checker.regex; |
| |
| import javax.lang.model.element.AnnotationMirror; |
| import javax.lang.model.element.ExecutableElement; |
| import org.checkerframework.dataflow.analysis.ConditionalTransferResult; |
| import org.checkerframework.dataflow.analysis.RegularTransferResult; |
| import org.checkerframework.dataflow.analysis.TransferInput; |
| import org.checkerframework.dataflow.analysis.TransferResult; |
| import org.checkerframework.dataflow.cfg.node.ClassNameNode; |
| import org.checkerframework.dataflow.cfg.node.GreaterThanNode; |
| import org.checkerframework.dataflow.cfg.node.GreaterThanOrEqualNode; |
| import org.checkerframework.dataflow.cfg.node.IntegerLiteralNode; |
| import org.checkerframework.dataflow.cfg.node.LessThanNode; |
| import org.checkerframework.dataflow.cfg.node.LessThanOrEqualNode; |
| import org.checkerframework.dataflow.cfg.node.MethodAccessNode; |
| import org.checkerframework.dataflow.cfg.node.MethodInvocationNode; |
| import org.checkerframework.dataflow.cfg.node.Node; |
| import org.checkerframework.dataflow.expression.JavaExpression; |
| import org.checkerframework.dataflow.util.NodeUtils; |
| import org.checkerframework.framework.flow.CFAbstractAnalysis; |
| import org.checkerframework.framework.flow.CFStore; |
| import org.checkerframework.framework.flow.CFTransfer; |
| import org.checkerframework.framework.flow.CFValue; |
| import org.checkerframework.javacutil.ElementUtils; |
| import org.checkerframework.javacutil.TreeUtils; |
| |
| /** The transfer function for the Regex Checker. */ |
| public class RegexTransfer extends CFTransfer { |
| |
| // isRegex and asRegex are tested as signatures (string name plus formal parameters), not |
| // ExecutableElement, because they exist in two packages: |
| // org.checkerframework.checker.regex.util.RegexUtil.isRegex(String,int) |
| // org.plumelib.util.RegexUtil.isRegex(String,int) |
| // and org.plumelib.util might not be on the classpath. |
| private static final String IS_REGEX_METHOD_NAME = "isRegex"; |
| private static final String AS_REGEX_METHOD_NAME = "asRegex"; |
| |
| /** The MatchResult.groupCount() method. */ |
| private final ExecutableElement matchResultgroupCount; |
| |
| /** Create the transfer function for the Regex Checker. */ |
| public RegexTransfer(CFAbstractAnalysis<CFValue, CFStore, CFTransfer> analysis) { |
| super(analysis); |
| this.matchResultgroupCount = |
| TreeUtils.getMethod( |
| "java.util.regex.MatchResult", |
| "groupCount", |
| 0, |
| analysis.getTypeFactory().getProcessingEnv()); |
| } |
| |
| // TODO: These are special cases for isRegex(String, int) and asRegex(String, int). They should |
| // be replaced by adding an @EnsuresQualifierIf annotation that supports specifying attributes. |
| @Override |
| public TransferResult<CFValue, CFStore> visitMethodInvocation( |
| MethodInvocationNode n, TransferInput<CFValue, CFStore> in) { |
| TransferResult<CFValue, CFStore> result = super.visitMethodInvocation(n, in); |
| |
| // refine result for some helper methods |
| MethodAccessNode target = n.getTarget(); |
| ExecutableElement method = target.getMethod(); |
| Node receiver = target.getReceiver(); |
| if (receiver instanceof ClassNameNode) { |
| ClassNameNode cnn = (ClassNameNode) receiver; |
| String receiverName = cnn.getElement().toString(); |
| if (isRegexUtil(receiverName)) { |
| result = handleRegexUtil(n, method, result); |
| } |
| } |
| return result; |
| } |
| |
| private TransferResult<CFValue, CFStore> handleRegexUtil( |
| MethodInvocationNode n, ExecutableElement method, TransferResult<CFValue, CFStore> result) { |
| RegexAnnotatedTypeFactory factory = (RegexAnnotatedTypeFactory) analysis.getTypeFactory(); |
| if (ElementUtils.matchesElement(method, IS_REGEX_METHOD_NAME, String.class, int.class)) { |
| // RegexUtil.isRegex(s, groups) method |
| // (No special case is needed for isRegex(String) because of |
| // the annotation on that method's definition.) |
| |
| CFStore thenStore = result.getRegularStore(); |
| CFStore elseStore = thenStore.copy(); |
| ConditionalTransferResult<CFValue, CFStore> newResult = |
| new ConditionalTransferResult<>(result.getResultValue(), thenStore, elseStore); |
| JavaExpression firstParam = JavaExpression.fromNode(n.getArgument(0)); |
| |
| // add annotation with correct group count (if possible, |
| // regex annotation without count otherwise) |
| Node count = n.getArgument(1); |
| int groupCount; |
| if (count instanceof IntegerLiteralNode) { |
| IntegerLiteralNode iln = (IntegerLiteralNode) count; |
| groupCount = iln.getValue(); |
| } else { |
| groupCount = 0; |
| } |
| AnnotationMirror regexAnnotation = factory.createRegexAnnotation(groupCount); |
| thenStore.insertValue(firstParam, regexAnnotation); |
| return newResult; |
| } else if (ElementUtils.matchesElement(method, AS_REGEX_METHOD_NAME, String.class, int.class)) { |
| // RegexUtil.asRegex(s, groups) method |
| // (No special case is needed for asRegex(String) because of |
| // the annotation on that method's definition.) |
| |
| // add annotation with correct group count (if possible, |
| // regex annotation without count otherwise) |
| AnnotationMirror regexAnnotation; |
| Node count = n.getArgument(1); |
| int groupCount; |
| if (count instanceof IntegerLiteralNode) { |
| IntegerLiteralNode iln = (IntegerLiteralNode) count; |
| groupCount = iln.getValue(); |
| } else { |
| groupCount = 0; |
| } |
| regexAnnotation = factory.createRegexAnnotation(groupCount); |
| |
| CFValue newResultValue = |
| analysis.createSingleAnnotationValue( |
| regexAnnotation, result.getResultValue().getUnderlyingType()); |
| return new RegularTransferResult<>(newResultValue, result.getRegularStore()); |
| } |
| return result; |
| } |
| |
| @Override |
| public TransferResult<CFValue, CFStore> visitLessThan( |
| LessThanNode n, TransferInput<CFValue, CFStore> in) { |
| // Look for: constant < mat.groupCount() |
| // Make mat be @Regex(constant + 1) |
| TransferResult<CFValue, CFStore> res = super.visitLessThan(n, in); |
| return handleMatcherGroupCount(n.getRightOperand(), n.getLeftOperand(), false, res); |
| } |
| |
| @Override |
| public TransferResult<CFValue, CFStore> visitLessThanOrEqual( |
| LessThanOrEqualNode n, TransferInput<CFValue, CFStore> in) { |
| // Look for: constant <= mat.groupCount() |
| // Make mat be @Regex(constant) |
| TransferResult<CFValue, CFStore> res = super.visitLessThanOrEqual(n, in); |
| return handleMatcherGroupCount(n.getRightOperand(), n.getLeftOperand(), true, res); |
| } |
| |
| @Override |
| public TransferResult<CFValue, CFStore> visitGreaterThan( |
| GreaterThanNode n, TransferInput<CFValue, CFStore> in) { |
| |
| TransferResult<CFValue, CFStore> res = super.visitGreaterThan(n, in); |
| return handleMatcherGroupCount(n.getLeftOperand(), n.getRightOperand(), false, res); |
| } |
| |
| @Override |
| public TransferResult<CFValue, CFStore> visitGreaterThanOrEqual( |
| GreaterThanOrEqualNode n, TransferInput<CFValue, CFStore> in) { |
| // Look for: mat.groupCount() >= constant |
| // Make mat be @Regex(constant) |
| TransferResult<CFValue, CFStore> res = super.visitGreaterThanOrEqual(n, in); |
| return handleMatcherGroupCount(n.getLeftOperand(), n.getRightOperand(), true, res); |
| } |
| |
| /** |
| * See whether possibleMatcher is a call of groupCount on a Matcher and possibleConstant is a |
| * constant. If so, annotate the matcher as constant + 1 if !isAlsoEqual constant if isAlsoEqual |
| * |
| * @param possibleMatcher the Node that might be a call of Matcher.groupCount() |
| * @param possibleConstant the Node that might be a constant |
| * @param isAlsoEqual whether the comparison operation is strict or reflexive |
| * @param resultIn TransferResult |
| * @return the possibly refined output TransferResult |
| */ |
| private TransferResult<CFValue, CFStore> handleMatcherGroupCount( |
| Node possibleMatcher, |
| Node possibleConstant, |
| boolean isAlsoEqual, |
| TransferResult<CFValue, CFStore> resultIn) { |
| if (!(possibleMatcher instanceof MethodInvocationNode)) { |
| return resultIn; |
| } |
| if (!(possibleConstant instanceof IntegerLiteralNode)) { |
| return resultIn; |
| } |
| |
| if (!NodeUtils.isMethodInvocation( |
| possibleMatcher, matchResultgroupCount, analysis.getTypeFactory().getProcessingEnv())) { |
| return resultIn; |
| } |
| |
| MethodAccessNode methodAccessNode = ((MethodInvocationNode) possibleMatcher).getTarget(); |
| Node receiver = methodAccessNode.getReceiver(); |
| |
| JavaExpression matcherReceiver = JavaExpression.fromNode(receiver); |
| |
| IntegerLiteralNode iln = (IntegerLiteralNode) possibleConstant; |
| int groupCount; |
| if (isAlsoEqual) { |
| groupCount = iln.getValue(); |
| } else { |
| groupCount = iln.getValue() + 1; |
| } |
| |
| CFStore thenStore = resultIn.getRegularStore(); |
| CFStore elseStore = thenStore.copy(); |
| ConditionalTransferResult<CFValue, CFStore> newResult = |
| new ConditionalTransferResult<>(resultIn.getResultValue(), thenStore, elseStore); |
| RegexAnnotatedTypeFactory factory = (RegexAnnotatedTypeFactory) analysis.getTypeFactory(); |
| |
| AnnotationMirror regexAnnotation = factory.createRegexAnnotation(groupCount); |
| thenStore.insertValue(matcherReceiver, regexAnnotation); |
| |
| return newResult; |
| } |
| |
| /** |
| * Returns true if the given receiver is a class named "RegexUtil". Examples of such classes are |
| * org.checkerframework.checker.regex.util.RegexUtil and org.plumelib.util.RegexUtil, and the user |
| * might copy one into their own project. |
| * |
| * @param receiver some string |
| * @return true if the given receiver is a class named "RegexUtil" |
| */ |
| private boolean isRegexUtil(String receiver) { |
| return receiver.equals("RegexUtil") || receiver.endsWith(".RegexUtil"); |
| } |
| } |