package org.checkerframework.checker.index.samelen; import com.sun.source.util.TreePath; import java.util.ArrayList; import java.util.List; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.type.TypeKind; import org.checkerframework.checker.index.IndexUtil; import org.checkerframework.checker.index.qual.SameLen; import org.checkerframework.dataflow.analysis.ConditionalTransferResult; import org.checkerframework.dataflow.analysis.FlowExpressions; import org.checkerframework.dataflow.analysis.FlowExpressions.Receiver; import org.checkerframework.dataflow.analysis.TransferInput; import org.checkerframework.dataflow.analysis.TransferResult; import org.checkerframework.dataflow.cfg.node.ArrayCreationNode; import org.checkerframework.dataflow.cfg.node.AssignmentNode; import org.checkerframework.dataflow.cfg.node.FieldAccessNode; import org.checkerframework.dataflow.cfg.node.Node; import org.checkerframework.framework.flow.CFAnalysis; import org.checkerframework.framework.flow.CFStore; import org.checkerframework.framework.flow.CFTransfer; import org.checkerframework.framework.flow.CFValue; import org.checkerframework.framework.util.FlowExpressionParseUtil; import org.checkerframework.javacutil.AnnotationUtils; /** * The transfer function for the SameLen checker. Contains three cases: * * <ol> * <li>"b = new T[a.length]" implies that b is the same length as a. * <li>after "if (a.length == b.length)", a and b have the same length. * <li>after "if (a == b)", a and b have the same length, if they are arrays. * </ol> */ public class SameLenTransfer extends CFTransfer { // The ATF (Annotated Type Factory). private SameLenAnnotatedTypeFactory aTypeFactory; /** Shorthand for SameLenUnknown.class. */ private AnnotationMirror UNKNOWN; private CFAnalysis analysis; public SameLenTransfer(CFAnalysis analysis) { super(analysis); this.analysis = analysis; aTypeFactory = (SameLenAnnotatedTypeFactory) analysis.getTypeFactory(); UNKNOWN = aTypeFactory.UNKNOWN; } @Override public TransferResult<CFValue, CFStore> visitAssignment( AssignmentNode node, TransferInput<CFValue, CFStore> in) { TransferResult<CFValue, CFStore> result = super.visitAssignment(node, in); // Handle b = new T[a.length] if (node.getExpression() instanceof ArrayCreationNode) { ArrayCreationNode acNode = (ArrayCreationNode) node.getExpression(); if (acNode.getDimensions().size() == 1 && isArrayLengthAccess(acNode.getDimension(0))) { // "new T[a.length]" is the right hand side of the assignment. FieldAccessNode arrayLengthNode = (FieldAccessNode) acNode.getDimension(0); Node arrayLengthNodeReceiver = arrayLengthNode.getReceiver(); // arrayLengthNode is known to be "new T[arrayLengthNodeReceiver.length]" // targetRec is the receiver for the left hand side of the assignment. Receiver targetRec = FlowExpressions.internalReprOf(analysis.getTypeFactory(), node.getTarget()); Receiver otherRec = FlowExpressions.internalReprOf( analysis.getTypeFactory(), arrayLengthNodeReceiver); AnnotationMirror arrayLengthNodeAnnotation = aTypeFactory .getAnnotatedType(arrayLengthNodeReceiver.getTree()) .getAnnotationInHierarchy(UNKNOWN); AnnotationMirror combinedSameLen = aTypeFactory.createCombinedSameLen( targetRec, otherRec, UNKNOWN, arrayLengthNodeAnnotation); propagateCombinedSameLen(combinedSameLen, node, result.getRegularStore()); return result; } } AnnotationMirror rightAnno = aTypeFactory .getAnnotatedType(node.getExpression().getTree()) .getAnnotationInHierarchy(UNKNOWN); // If the left side of the assignment is an array, then have both the right and left side be SameLen // of each other. Receiver targetRec = FlowExpressions.internalReprOf(analysis.getTypeFactory(), node.getTarget()); Receiver exprRec = FlowExpressions.internalReprOf(analysis.getTypeFactory(), node.getExpression()); if (node.getTarget().getType().getKind() == TypeKind.ARRAY || (rightAnno != null && AnnotationUtils.areSameByClass(rightAnno, SameLen.class))) { AnnotationMirror rightAnnoOrUnknown = rightAnno == null ? UNKNOWN : rightAnno; AnnotationMirror combinedSameLen = aTypeFactory.createCombinedSameLen( targetRec, exprRec, UNKNOWN, rightAnnoOrUnknown); propagateCombinedSameLen(combinedSameLen, node, result.getRegularStore()); } return result; } /** * Insert combinedSameLen into the store as the SameLen type of each array listed in * combinedSameLen. * * @param combinedSameLen A Samelen annotation. Not just an annotation in the SameLen hierarchy; * this annotation MUST be @SameLen(). * @param node The node in the tree where the combination is happening. Used for context. * @param store The store to modify */ private void propagateCombinedSameLen( AnnotationMirror combinedSameLen, Node node, CFStore store) { TreePath currentPath = aTypeFactory.getPath(node.getTree()); if (currentPath == null) { return; } for (String s : IndexUtil.getValueOfAnnotationWithStringArgument(combinedSameLen)) { Receiver recS; try { recS = aTypeFactory.getReceiverFromJavaExpressionString(s, currentPath); } catch (FlowExpressionParseUtil.FlowExpressionParseException e) { continue; } store.clearValue(recS); store.insertValue(recS, combinedSameLen); } } /** Returns true if node is of the form "someArray.length". */ private boolean isArrayLengthAccess(Node node) { return (node instanceof FieldAccessNode && ((FieldAccessNode) node).getFieldName().equals("length") && ((FieldAccessNode) node).getReceiver().getType().getKind() == TypeKind.ARRAY); } /** * Handles refinement of equality comparisons. After "a.length == b.length" evaluates to true, a * and b have SameLen of each other in the store. */ private void refineEq(Node left, Node right, CFStore store) { List<Receiver> receivers = new ArrayList<>(); List<AnnotationMirror> annos = new ArrayList<>(); for (Node internal : splitAssignments(left)) { receivers.add(FlowExpressions.internalReprOf(analysis.getTypeFactory(), internal)); annos.add(getAnno(internal)); } for (Node internal : splitAssignments(right)) { receivers.add(FlowExpressions.internalReprOf(analysis.getTypeFactory(), internal)); annos.add(getAnno(internal)); } AnnotationMirror combinedSameLen = aTypeFactory.createCombinedSameLen(receivers, annos); propagateCombinedSameLen(combinedSameLen, left, store); } /** * Return n's annotation from the SameLen hierarchy. * * <p>analysis.getValue fails if called on an lvalue. However, this method needs to always * succeed, even when n is an lvalue. Consider this code: * * <pre>{@code if ( (a = b) == c) {...}}</pre> * * where a, b, and c are all arrays, and a has type {@literal @}SameLen("d"). Afterwards, all * three should have the type {@literal @}SameLen({"a", "b", "c", "d"}), but in order to * accomplish this, this method must return the type of a, which is an lvalue. */ AnnotationMirror getAnno(Node n) { if (n.isLValue()) { return aTypeFactory.getAnnotatedType(n.getTree()).getAnnotationInHierarchy(UNKNOWN); } else { CFValue cfValue = analysis.getValue(n); if (cfValue == null) { return UNKNOWN; } if (cfValue.getAnnotations().size() == 1) { return cfValue.getAnnotations().iterator().next(); } return UNKNOWN; } } /** Implements the transfer rules for both equal nodes and not-equals nodes. */ @Override protected TransferResult<CFValue, CFStore> strengthenAnnotationOfEqualTo( TransferResult<CFValue, CFStore> result, Node firstNode, Node secondNode, CFValue firstValue, CFValue secondValue, boolean notEqualTo) { CFStore equalStore = notEqualTo ? result.getElseStore() : result.getThenStore(); if (isArrayLengthAccess(firstNode) && isArrayLengthAccess(secondNode)) { // Refinement in the else store if this is a.length == b.length. refineEq( ((FieldAccessNode) firstNode).getReceiver(), ((FieldAccessNode) secondNode).getReceiver(), equalStore); } else if (firstNode.getType().getKind() == TypeKind.ARRAY || secondNode.getType().getKind() == TypeKind.ARRAY) { refineEq(firstNode, secondNode, equalStore); } return new ConditionalTransferResult<>( result.getResultValue(), result.getThenStore(), result.getElseStore()); } }