package org.checkerframework.checker.regex;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.dataflow.analysis.ConditionalTransferResult;
import org.checkerframework.dataflow.analysis.FlowExpressions;
import org.checkerframework.dataflow.analysis.FlowExpressions.Receiver;
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.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.TypesUtils;
public class RegexTransfer extends CFTransfer {
private static final String IS_REGEX_METHOD_NAME = "isRegex";
private static final String AS_REGEX_METHOD_NAME = "asRegex";
private static final String GROUP_COUNT_METHOD_NAME = "groupCount";
public RegexTransfer(CFAbstractAnalysis<CFValue, CFStore, CFTransfer> analysis) {
super(analysis);
}
// 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);
Receiver firstParam =
FlowExpressions.internalReprOf(
factory.getContext().getAnnotationProvider(), 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, in, 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, in, 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, in, 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, in, 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 in the TransferInput
* @param resultIn TransferResult
* @return the possibly refined output TransferResult
*/
private TransferResult<CFValue, CFStore> handleMatcherGroupCount(
Node possibleMatcher,
Node possibleConstant,
boolean isAlsoEqual,
TransferInput<CFValue, CFStore> in,
TransferResult<CFValue, CFStore> resultIn) {
if (!(possibleMatcher instanceof MethodInvocationNode)) {
return resultIn;
}
if (!(possibleConstant instanceof IntegerLiteralNode)) {
return resultIn;
}
MethodAccessNode methodAccessNode = ((MethodInvocationNode) possibleMatcher).getTarget();
ExecutableElement method = methodAccessNode.getMethod();
Node receiver = methodAccessNode.getReceiver();
if (!isMatcherGroupCountMethod(method, receiver)) {
return resultIn;
}
Receiver matcherReceiver =
FlowExpressions.internalReprOf(analysis.getTypeFactory(), 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;
}
private boolean isMatcherGroupCountMethod(ExecutableElement method, Node receiver) {
if (ElementUtils.matchesElement(method, GROUP_COUNT_METHOD_NAME)) {
TypeMirror matcherType = receiver.getType();
if (matcherType.getKind() != TypeKind.DECLARED) {
return false;
}
DeclaredType matcherDT = (DeclaredType) matcherType;
return TypesUtils.getQualifiedName(matcherDT)
.contentEquals(java.util.regex.Matcher.class.getCanonicalName());
}
return false;
}
/** Returns true if the given receiver is a class named "RegexUtil". */
private boolean isRegexUtil(String receiver) {
return receiver.equals("RegexUtil") || receiver.endsWith(".RegexUtil");
}
}