| package org.checkerframework.common.value; |
| |
| import com.sun.source.tree.ExpressionTree; |
| import com.sun.source.tree.Tree; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| import javax.lang.model.element.AnnotationMirror; |
| import javax.lang.model.element.Element; |
| import javax.lang.model.element.ExecutableElement; |
| import javax.lang.model.type.TypeKind; |
| import javax.lang.model.type.TypeMirror; |
| import org.checkerframework.common.value.qual.ArrayLen; |
| import org.checkerframework.common.value.qual.ArrayLenRange; |
| import org.checkerframework.common.value.qual.StringVal; |
| import org.checkerframework.common.value.util.NumberMath; |
| import org.checkerframework.common.value.util.NumberUtils; |
| import org.checkerframework.common.value.util.Range; |
| 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.BitwiseAndNode; |
| import org.checkerframework.dataflow.cfg.node.BitwiseComplementNode; |
| import org.checkerframework.dataflow.cfg.node.BitwiseOrNode; |
| import org.checkerframework.dataflow.cfg.node.BitwiseXorNode; |
| import org.checkerframework.dataflow.cfg.node.ConditionalAndNode; |
| import org.checkerframework.dataflow.cfg.node.ConditionalNotNode; |
| import org.checkerframework.dataflow.cfg.node.ConditionalOrNode; |
| import org.checkerframework.dataflow.cfg.node.EqualToNode; |
| import org.checkerframework.dataflow.cfg.node.FieldAccessNode; |
| import org.checkerframework.dataflow.cfg.node.FloatingDivisionNode; |
| import org.checkerframework.dataflow.cfg.node.FloatingRemainderNode; |
| import org.checkerframework.dataflow.cfg.node.GreaterThanNode; |
| import org.checkerframework.dataflow.cfg.node.GreaterThanOrEqualNode; |
| import org.checkerframework.dataflow.cfg.node.IntegerDivisionNode; |
| import org.checkerframework.dataflow.cfg.node.IntegerRemainderNode; |
| import org.checkerframework.dataflow.cfg.node.LeftShiftNode; |
| 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.cfg.node.NotEqualNode; |
| import org.checkerframework.dataflow.cfg.node.NumericalAdditionNode; |
| import org.checkerframework.dataflow.cfg.node.NumericalMinusNode; |
| import org.checkerframework.dataflow.cfg.node.NumericalMultiplicationNode; |
| import org.checkerframework.dataflow.cfg.node.NumericalPlusNode; |
| import org.checkerframework.dataflow.cfg.node.NumericalSubtractionNode; |
| import org.checkerframework.dataflow.cfg.node.SignedRightShiftNode; |
| import org.checkerframework.dataflow.cfg.node.StringConcatenateAssignmentNode; |
| import org.checkerframework.dataflow.cfg.node.StringConcatenateNode; |
| import org.checkerframework.dataflow.cfg.node.StringConversionNode; |
| import org.checkerframework.dataflow.cfg.node.StringLiteralNode; |
| import org.checkerframework.dataflow.cfg.node.UnsignedRightShiftNode; |
| import org.checkerframework.dataflow.expression.JavaExpression; |
| import org.checkerframework.dataflow.expression.Unknown; |
| import org.checkerframework.dataflow.util.NodeUtils; |
| import org.checkerframework.framework.flow.CFAbstractAnalysis; |
| import org.checkerframework.framework.flow.CFAbstractStore; |
| import org.checkerframework.framework.flow.CFStore; |
| import org.checkerframework.framework.flow.CFTransfer; |
| import org.checkerframework.framework.flow.CFValue; |
| import org.checkerframework.framework.type.QualifierHierarchy; |
| import org.checkerframework.javacutil.AnnotationUtils; |
| import org.checkerframework.javacutil.BugInCF; |
| import org.checkerframework.javacutil.ElementUtils; |
| import org.checkerframework.javacutil.TreeUtils; |
| import org.checkerframework.javacutil.TypesUtils; |
| import org.plumelib.util.CollectionsPlume; |
| |
| /** The transfer class for the Value Checker. */ |
| public class ValueTransfer extends CFTransfer { |
| /** The Value type factory. */ |
| protected final ValueAnnotatedTypeFactory atypeFactory; |
| /** The Value qualifier hierarchy. */ |
| protected final QualifierHierarchy hierarchy; |
| |
| /** |
| * Create a new ValueTransfer. |
| * |
| * @param analysis the corresponding analysis |
| */ |
| public ValueTransfer(CFAbstractAnalysis<CFValue, CFStore, CFTransfer> analysis) { |
| super(analysis); |
| atypeFactory = (ValueAnnotatedTypeFactory) analysis.getTypeFactory(); |
| hierarchy = atypeFactory.getQualifierHierarchy(); |
| } |
| |
| /** Returns a range of possible lengths for an integer from a range, as casted to a String. */ |
| private Range getIntRangeStringLengthRange(Node subNode, TransferInput<CFValue, CFStore> p) { |
| Range valueRange = getIntRange(subNode, p); |
| |
| // Get lengths of the bounds |
| int fromLength = Long.toString(valueRange.from).length(); |
| int toLength = Long.toString(valueRange.to).length(); |
| |
| int lowerLength = Math.min(fromLength, toLength); |
| // In case the range contains 0, the minimum length is 1 even if both bounds are longer |
| if (valueRange.contains(0)) { |
| lowerLength = 1; |
| } |
| |
| int upperLength = Math.max(fromLength, toLength); |
| |
| return Range.create(lowerLength, upperLength); |
| } |
| |
| /** |
| * Returns a range of possible lengths for {@code subNode}, as casted to a String. |
| * |
| * @param subNode some subnode of {@code p} |
| * @param p TransferInput |
| * @return a range of possible lengths for {@code subNode}, as casted to a String |
| */ |
| private Range getStringLengthRange(Node subNode, TransferInput<CFValue, CFStore> p) { |
| CFValue value = p.getValueOfSubNode(subNode); |
| |
| AnnotationMirror anno = getValueAnnotation(value); |
| if (anno == null) { |
| return null; |
| } |
| String annoName = AnnotationUtils.annotationName(anno); |
| if (annoName.equals(ValueAnnotatedTypeFactory.ARRAYLENRANGE_NAME)) { |
| return atypeFactory.getRange(anno); |
| } else if (annoName.equals(ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) { |
| return Range.NOTHING; |
| } |
| |
| TypeKind subNodeTypeKind = subNode.getType().getKind(); |
| |
| // handle values converted to string (ints, longs, longs with @IntRange) |
| if (subNode instanceof StringConversionNode) { |
| return getStringLengthRange(((StringConversionNode) subNode).getOperand(), p); |
| } else if (isIntRange(subNode, p)) { |
| return getIntRangeStringLengthRange(subNode, p); |
| } else if (subNodeTypeKind == TypeKind.INT) { |
| // ints are between 1 and 11 characters long |
| return Range.create(1, 11); |
| } else if (subNodeTypeKind == TypeKind.LONG) { |
| // longs are between 1 and 20 characters long |
| return Range.create(1, 20); |
| } |
| |
| return Range.create(0, Integer.MAX_VALUE); |
| } |
| |
| /** |
| * Returns a list of possible lengths for {@code subNode}, as casted to a String. Returns null if |
| * {@code subNode}'s type is top/unknown. Returns an empty list if {@code subNode}'s type is |
| * bottom. |
| */ |
| private List<Integer> getStringLengths(Node subNode, TransferInput<CFValue, CFStore> p) { |
| |
| CFValue value = p.getValueOfSubNode(subNode); |
| AnnotationMirror anno = getValueAnnotation(value); |
| if (anno == null) { |
| return null; |
| } |
| String annoName = AnnotationUtils.annotationName(anno); |
| if (annoName.equals(ValueAnnotatedTypeFactory.ARRAYLEN_NAME)) { |
| return atypeFactory.getArrayLength(anno); |
| } else if (annoName.equals(ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) { |
| return Collections.emptyList(); |
| } |
| |
| TypeKind subNodeTypeKind = subNode.getType().getKind(); |
| |
| // handle values converted to string (characters, bytes, shorts, ints with @IntRange) |
| if (subNode instanceof StringConversionNode) { |
| return getStringLengths(((StringConversionNode) subNode).getOperand(), p); |
| } else if (subNodeTypeKind == TypeKind.CHAR) { |
| // characters always have length 1 |
| return Collections.singletonList(1); |
| } else if (isIntRange(subNode, p)) { |
| // Try to get a list of lengths from a range of integer values converted to string @IntVal is |
| // not checked for, because if it is present, we would already have the actual string values |
| Range lengthRange = getIntRangeStringLengthRange(subNode, p); |
| return ValueCheckerUtils.getValuesFromRange(lengthRange, Integer.class); |
| } else if (subNodeTypeKind == TypeKind.BYTE) { |
| // bytes are between 1 and 4 characters long |
| return ValueCheckerUtils.getValuesFromRange(Range.create(1, 4), Integer.class); |
| } else if (subNodeTypeKind == TypeKind.SHORT) { |
| // shorts are between 1 and 6 characters long |
| return ValueCheckerUtils.getValuesFromRange(Range.create(1, 6), Integer.class); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Returns a list of possible values for {@code subNode}, as casted to a String. Returns null if |
| * {@code subNode}'s type is top/unknown. Returns an empty list if {@code subNode}'s type is |
| * bottom. |
| * |
| * @param subNode a subNode of p |
| * @param p TransferInput |
| * @return a list of possible values for {@code subNode} or null |
| */ |
| private List<String> getStringValues(Node subNode, TransferInput<CFValue, CFStore> p) { |
| CFValue value = p.getValueOfSubNode(subNode); |
| AnnotationMirror anno = getValueAnnotation(value); |
| if (anno == null) { |
| return null; |
| } |
| String annoName = AnnotationUtils.annotationName(anno); |
| switch (annoName) { |
| case ValueAnnotatedTypeFactory.UNKNOWN_NAME: |
| return null; |
| case ValueAnnotatedTypeFactory.BOTTOMVAL_NAME: |
| return Collections.emptyList(); |
| case ValueAnnotatedTypeFactory.STRINGVAL_NAME: |
| return atypeFactory.getStringValues(anno); |
| default: |
| // Do nothing. |
| } |
| |
| // @IntVal, @IntRange, @DoubleVal, @BoolVal (have to be converted to string) |
| List<? extends Object> values; |
| if (annoName.equals(ValueAnnotatedTypeFactory.BOOLVAL_NAME)) { |
| values = getBooleanValues(subNode, p); |
| } else if (subNode.getType().getKind() == TypeKind.CHAR) { |
| values = getCharValues(subNode, p); |
| } else if (subNode instanceof StringConversionNode) { |
| return getStringValues(((StringConversionNode) subNode).getOperand(), p); |
| } else if (isIntRange(subNode, p)) { |
| Range range = getIntRange(subNode, p); |
| List<Long> longValues = ValueCheckerUtils.getValuesFromRange(range, Long.class); |
| values = NumberUtils.castNumbers(subNode.getType(), longValues); |
| } else { |
| values = getNumericalValues(subNode, p); |
| } |
| if (values == null) { |
| return null; |
| } |
| List<String> stringValues = CollectionsPlume.mapList(Object::toString, values); |
| // Empty list means bottom value |
| return stringValues.isEmpty() ? Collections.singletonList("null") : stringValues; |
| } |
| |
| /** |
| * Create a @BoolVal CFValue for the given boolean value. |
| * |
| * @param value the value for the @BoolVal annotation |
| * @return a @BoolVal CFValue for the given boolean value |
| */ |
| private CFValue createBooleanCFValue(boolean value) { |
| return analysis.createSingleAnnotationValue( |
| value ? atypeFactory.BOOLEAN_TRUE : atypeFactory.BOOLEAN_FALSE, |
| atypeFactory.types.getPrimitiveType(TypeKind.BOOLEAN)); |
| } |
| |
| /** |
| * Get the unique possible boolean value from @BoolVal. Returns null if that is not the case |
| * (including if the CFValue is not @BoolVal). |
| * |
| * @param value a CFValue |
| * @return theboolean if {@code value} represents a single boolean value; otherwise null |
| */ |
| private Boolean getBooleanValue(CFValue value) { |
| AnnotationMirror boolAnno = |
| AnnotationUtils.getAnnotationByName( |
| value.getAnnotations(), ValueAnnotatedTypeFactory.BOOLVAL_NAME); |
| return atypeFactory.getBooleanValue(boolAnno); |
| } |
| |
| /** |
| * Get possible boolean values for a node. Returns null if there is no estimate, because the |
| * node's value is not @BoolVal. |
| * |
| * @param subNode the node whose value to obtain |
| * @param p the transfer input in which to look up values |
| * @return the possible boolean values for the node |
| */ |
| private List<Boolean> getBooleanValues(Node subNode, TransferInput<CFValue, CFStore> p) { |
| CFValue value = p.getValueOfSubNode(subNode); |
| AnnotationMirror intAnno = |
| AnnotationUtils.getAnnotationByName( |
| value.getAnnotations(), ValueAnnotatedTypeFactory.BOOLVAL_NAME); |
| return atypeFactory.getBooleanValues(intAnno); |
| } |
| |
| /** Get possible char values from annotation @IntRange or @IntVal. */ |
| private List<Character> getCharValues(Node subNode, TransferInput<CFValue, CFStore> p) { |
| CFValue value = p.getValueOfSubNode(subNode); |
| AnnotationMirror intAnno; |
| |
| intAnno = |
| AnnotationUtils.getAnnotationByName( |
| value.getAnnotations(), ValueAnnotatedTypeFactory.INTVAL_NAME); |
| if (intAnno != null) { |
| return atypeFactory.getCharValues(intAnno); |
| } |
| |
| if (atypeFactory.isIntRange(value.getAnnotations())) { |
| intAnno = |
| hierarchy.findAnnotationInHierarchy(value.getAnnotations(), atypeFactory.UNKNOWNVAL); |
| Range range = atypeFactory.getRange(intAnno); |
| return ValueCheckerUtils.getValuesFromRange(range, Character.class); |
| } |
| |
| return Collections.emptyList(); |
| } |
| |
| private AnnotationMirror getValueAnnotation(Node subNode, TransferInput<CFValue, CFStore> p) { |
| CFValue value = p.getValueOfSubNode(subNode); |
| return getValueAnnotation(value); |
| } |
| |
| /** |
| * Extract the Value Checker annotation from a CFValue object. |
| * |
| * @param cfValue a CFValue object |
| * @return the Value Checker annotation within cfValue |
| */ |
| private AnnotationMirror getValueAnnotation(CFValue cfValue) { |
| return hierarchy.findAnnotationInHierarchy(cfValue.getAnnotations(), atypeFactory.UNKNOWNVAL); |
| } |
| |
| /** |
| * Returns a list of possible values, or null if no estimate is available and any value is |
| * possible. |
| */ |
| private List<? extends Number> getNumericalValues( |
| Node subNode, TransferInput<CFValue, CFStore> p) { |
| AnnotationMirror valueAnno = getValueAnnotation(subNode, p); |
| return getNumericalValues(subNode, valueAnno); |
| } |
| |
| /** |
| * Returns the numerical values in valueAnno casted to the type of subNode. |
| * |
| * @param subNode node |
| * @param valueAnno annotation mirror |
| * @return the numerical values in valueAnno casted to the type of subNode |
| */ |
| private List<? extends Number> getNumericalValues(Node subNode, AnnotationMirror valueAnno) { |
| |
| if (valueAnno == null |
| || AnnotationUtils.areSameByName(valueAnno, ValueAnnotatedTypeFactory.UNKNOWN_NAME)) { |
| return null; |
| } else if (AnnotationUtils.areSameByName(valueAnno, ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) { |
| return Collections.emptyList(); |
| } |
| List<? extends Number> values; |
| if (AnnotationUtils.areSameByName(valueAnno, ValueAnnotatedTypeFactory.INTVAL_NAME)) { |
| values = atypeFactory.getIntValues(valueAnno); |
| } else if (AnnotationUtils.areSameByName(valueAnno, ValueAnnotatedTypeFactory.DOUBLEVAL_NAME)) { |
| values = atypeFactory.getDoubleValues(valueAnno); |
| } else { |
| return null; |
| } |
| return NumberUtils.castNumbers(subNode.getType(), values); |
| } |
| |
| /** Get possible integer range from annotation. */ |
| private Range getIntRange(Node subNode, TransferInput<CFValue, CFStore> p) { |
| AnnotationMirror val = getValueAnnotation(subNode, p); |
| return getIntRangeFromAnnotation(subNode, val); |
| } |
| |
| /** |
| * Returns the {@link Range} object corresponding to the annotation {@code val} casted to the type |
| * of {@code node}. |
| * |
| * @param node a node |
| * @param val annotation mirror |
| * @return the {@link Range} object corresponding to the annotation {@code val} casted to the type |
| * of {@code node}. |
| */ |
| private Range getIntRangeFromAnnotation(Node node, AnnotationMirror val) { |
| Range range; |
| if (val == null || AnnotationUtils.areSameByName(val, ValueAnnotatedTypeFactory.UNKNOWN_NAME)) { |
| range = Range.EVERYTHING; |
| } else if (atypeFactory.isIntRange(val)) { |
| range = atypeFactory.getRange(val); |
| } else if (AnnotationUtils.areSameByName(val, ValueAnnotatedTypeFactory.INTVAL_NAME)) { |
| List<Long> values = atypeFactory.getIntValues(val); |
| range = ValueCheckerUtils.getRangeFromValues(values); |
| } else if (AnnotationUtils.areSameByName(val, ValueAnnotatedTypeFactory.DOUBLEVAL_NAME)) { |
| List<Double> values = atypeFactory.getDoubleValues(val); |
| range = ValueCheckerUtils.getRangeFromValues(values); |
| } else if (AnnotationUtils.areSameByName(val, ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) { |
| return Range.NOTHING; |
| } else { |
| range = Range.EVERYTHING; |
| } |
| return NumberUtils.castRange(node.getType(), range); |
| } |
| |
| /** |
| * Returns true if subNode is annotated with {@code @IntRange}. |
| * |
| * @param subNode subNode of {@code p} |
| * @param p TransferInput |
| * @return true if this subNode is annotated with {@code @IntRange} |
| */ |
| private boolean isIntRange(Node subNode, TransferInput<CFValue, CFStore> p) { |
| CFValue value = p.getValueOfSubNode(subNode); |
| return atypeFactory.isIntRange(value.getAnnotations()); |
| } |
| |
| /** |
| * Returns true if {@code node} an integral type and is {@code anno} is {@code @UnknownVal}. |
| * |
| * @param node a node |
| * @param anno annotation mirror |
| * @return true if node is annotated with {@code @UnknownVal} and it is an integral type |
| */ |
| private boolean isIntegralUnknownVal(Node node, AnnotationMirror anno) { |
| return AnnotationUtils.areSameByName(anno, ValueAnnotatedTypeFactory.UNKNOWN_NAME) |
| && TypesUtils.isIntegralPrimitive(node.getType()); |
| } |
| |
| /** |
| * Returns true if this node is annotated with {@code @IntRange} or {@code @UnknownVal}. |
| * |
| * @param node the node to inspect |
| * @param p storage |
| * @return true if this node is annotated with {@code @IntRange} or {@code @UnknownVal} |
| */ |
| private boolean isIntRangeOrIntegralUnknownVal(Node node, TransferInput<CFValue, CFStore> p) { |
| if (isIntRange(node, p)) { |
| return true; |
| } |
| return isIntegralUnknownVal(node, getValueAnnotation(p.getValueOfSubNode(node))); |
| } |
| |
| /** |
| * Create a new transfer result based on the original result and the new annotation. |
| * |
| * @param result the original result |
| * @param resultAnno the new annotation |
| * @return the new transfer result |
| */ |
| private TransferResult<CFValue, CFStore> createNewResult( |
| TransferResult<CFValue, CFStore> result, AnnotationMirror resultAnno) { |
| CFValue newResultValue = |
| analysis.createSingleAnnotationValue( |
| resultAnno, result.getResultValue().getUnderlyingType()); |
| return new RegularTransferResult<>(newResultValue, result.getRegularStore()); |
| } |
| |
| /** Create a boolean transfer result. */ |
| private TransferResult<CFValue, CFStore> createNewResultBoolean( |
| CFStore thenStore, CFStore elseStore, List<Boolean> resultValues, TypeMirror underlyingType) { |
| AnnotationMirror boolVal = atypeFactory.createBooleanAnnotation(resultValues); |
| CFValue newResultValue = analysis.createSingleAnnotationValue(boolVal, underlyingType); |
| if (elseStore != null) { |
| return new ConditionalTransferResult<>(newResultValue, thenStore, elseStore); |
| } else { |
| return new RegularTransferResult<>(newResultValue, thenStore); |
| } |
| } |
| |
| @Override |
| public TransferResult<CFValue, CFStore> visitFieldAccess( |
| FieldAccessNode node, TransferInput<CFValue, CFStore> in) { |
| |
| TransferResult<CFValue, CFStore> result = super.visitFieldAccess(node, in); |
| refineArrayAtLengthAccess(node, result.getRegularStore()); |
| return result; |
| } |
| |
| @Override |
| public TransferResult<CFValue, CFStore> visitMethodInvocation( |
| MethodInvocationNode n, TransferInput<CFValue, CFStore> p) { |
| TransferResult<CFValue, CFStore> result = super.visitMethodInvocation(n, p); |
| refineAtLengthInvocation(n, result.getRegularStore()); |
| return result; |
| } |
| |
| /** |
| * If array.length is encountered, transform its @IntVal annotation into an @ArrayLen annotation |
| * for array. |
| */ |
| private void refineArrayAtLengthAccess(FieldAccessNode arrayLengthNode, CFStore store) { |
| if (!NodeUtils.isArrayLengthFieldAccess(arrayLengthNode)) { |
| return; |
| } |
| |
| refineAtLengthAccess(arrayLengthNode, arrayLengthNode.getReceiver(), store); |
| } |
| |
| /** |
| * If length method is invoked for a sequence, transform its @IntVal annotation into an @ArrayLen |
| * annotation. |
| * |
| * @param lengthNode the length method invocation node |
| * @param store the Checker Framework store |
| */ |
| private void refineAtLengthInvocation(MethodInvocationNode lengthNode, CFStore store) { |
| if (atypeFactory |
| .getMethodIdentifier() |
| .isStringLengthMethod(lengthNode.getTarget().getMethod())) { |
| MethodAccessNode methodAccessNode = lengthNode.getTarget(); |
| refineAtLengthAccess(lengthNode, methodAccessNode.getReceiver(), store); |
| } else if (atypeFactory |
| .getMethodIdentifier() |
| .isArrayGetLengthMethod(lengthNode.getTarget().getMethod())) { |
| Node node = lengthNode.getArguments().get(0); |
| refineAtLengthAccess(lengthNode, node, store); |
| } |
| } |
| |
| /** |
| * Gets a value checker annotation relevant for an array or a string. |
| * |
| * @param arrayOrStringNode the node whose annotation to return |
| * @return the value checker annotation for the array or a string |
| */ |
| private AnnotationMirror getArrayOrStringAnnotation(Node arrayOrStringNode) { |
| AnnotationMirror arrayOrStringAnno = |
| atypeFactory.getAnnotationMirror(arrayOrStringNode.getTree(), StringVal.class); |
| if (arrayOrStringAnno == null) { |
| arrayOrStringAnno = |
| atypeFactory.getAnnotationMirror(arrayOrStringNode.getTree(), ArrayLen.class); |
| } |
| if (arrayOrStringAnno == null) { |
| arrayOrStringAnno = |
| atypeFactory.getAnnotationMirror(arrayOrStringNode.getTree(), ArrayLenRange.class); |
| } |
| |
| return arrayOrStringAnno; |
| } |
| |
| /** |
| * Transform @IntVal or @IntRange annotations of a array or string length into an @ArrayLen |
| * or @ArrayLenRange annotation for the array or string. |
| * |
| * @param lengthNode an invocation of method {@code length} or an access of the {@code length} |
| * field |
| * @param receiverNode the receiver of {@code lengthNode} |
| * @param store the store to update |
| */ |
| private void refineAtLengthAccess(Node lengthNode, Node receiverNode, CFStore store) { |
| JavaExpression lengthExpr = JavaExpression.fromNode(lengthNode); |
| |
| // If the expression is not representable (for example if String.length() for some reason is |
| // not marked @Pure, then do not refine. |
| if (lengthExpr instanceof Unknown) { |
| return; |
| } |
| |
| CFValue value = store.getValue(lengthExpr); |
| if (value == null) { |
| return; |
| } |
| |
| AnnotationMirror lengthAnno = getValueAnnotation(value); |
| if (lengthAnno == null) { |
| return; |
| } |
| if (AnnotationUtils.areSameByName(lengthAnno, ValueAnnotatedTypeFactory.BOTTOMVAL_NAME)) { |
| // If the length is bottom, then this is dead code, so the receiver type |
| // should also be bottom. |
| JavaExpression receiver = JavaExpression.fromNode(receiverNode); |
| store.insertValue(receiver, lengthAnno); |
| return; |
| } |
| |
| RangeOrListOfValues rolv; |
| if (atypeFactory.isIntRange(lengthAnno)) { |
| rolv = new RangeOrListOfValues(atypeFactory.getRange(lengthAnno)); |
| } else if (AnnotationUtils.areSameByName(lengthAnno, ValueAnnotatedTypeFactory.INTVAL_NAME)) { |
| List<Long> lengthValues = atypeFactory.getIntValues(lengthAnno); |
| rolv = new RangeOrListOfValues(RangeOrListOfValues.convertLongsToInts(lengthValues)); |
| } else { |
| return; |
| } |
| AnnotationMirror newRecAnno = rolv.createAnnotation(atypeFactory); |
| AnnotationMirror oldRecAnno = getArrayOrStringAnnotation(receiverNode); |
| |
| AnnotationMirror combinedRecAnno; |
| // If the receiver doesn't have an @ArrayLen annotation, use the new annotation. |
| // If it does have an annotation, combine the facts known about the receiver |
| // with the facts known about its length using GLB. |
| if (oldRecAnno == null) { |
| combinedRecAnno = newRecAnno; |
| } else { |
| combinedRecAnno = hierarchy.greatestLowerBound(oldRecAnno, newRecAnno); |
| } |
| JavaExpression receiver = JavaExpression.fromNode(receiverNode); |
| store.insertValue(receiver, combinedRecAnno); |
| } |
| |
| @Override |
| public TransferResult<CFValue, CFStore> visitStringConcatenateAssignment( |
| StringConcatenateAssignmentNode n, TransferInput<CFValue, CFStore> p) { |
| TransferResult<CFValue, CFStore> result = super.visitStringConcatenateAssignment(n, p); |
| return stringConcatenation(n.getLeftOperand(), n.getRightOperand(), p, result); |
| } |
| |
| @Override |
| public TransferResult<CFValue, CFStore> visitStringConcatenate( |
| StringConcatenateNode n, TransferInput<CFValue, CFStore> p) { |
| TransferResult<CFValue, CFStore> result = super.visitStringConcatenate(n, p); |
| return stringConcatenation(n.getLeftOperand(), n.getRightOperand(), p, result); |
| } |
| |
| /** |
| * Calculates possible lengths of a result of string concatenation of strings with known lengths. |
| */ |
| private List<Integer> calculateLengthAddition( |
| List<Integer> leftLengths, List<Integer> rightLengths) { |
| List<Integer> result = new ArrayList<>(leftLengths.size() * rightLengths.size()); |
| |
| for (int left : leftLengths) { |
| for (int right : rightLengths) { |
| long resultLength = (long) left + right; |
| // Lengths not fitting into int are not allowed |
| if (resultLength <= Integer.MAX_VALUE) { |
| result.add((int) resultLength); |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Calculates a range of possible lengths of a result of string concatenation of strings with |
| * known ranges of lengths. |
| */ |
| private Range calculateLengthRangeAddition(Range leftLengths, Range rightLengths) { |
| return leftLengths.plus(rightLengths).intersect(Range.INT_EVERYTHING); |
| } |
| |
| /** |
| * Checks whether or not the passed node is nullable. This superficial check assumes that every |
| * node is nullable unless it is a primitive, String literal, or compile-time constant. |
| * |
| * @return false if the node's run-time can't be null; true if the node's run-time value may be |
| * null, or if this method is not precise enough |
| */ |
| private boolean isNullable(Node node) { |
| if (node instanceof StringConversionNode) { |
| if (((StringConversionNode) node).getOperand().getType().getKind().isPrimitive()) { |
| return false; |
| } |
| } else if (node instanceof StringLiteralNode) { |
| return false; |
| } else if (node instanceof StringConcatenateNode) { |
| return false; |
| } |
| |
| Element element = TreeUtils.elementFromUse((ExpressionTree) node.getTree()); |
| return !ElementUtils.isCompileTimeConstant(element); |
| } |
| |
| /** Creates an annotation for a result of string concatenation. */ |
| private AnnotationMirror createAnnotationForStringConcatenation( |
| Node leftOperand, Node rightOperand, TransferInput<CFValue, CFStore> p) { |
| |
| // Try using sets of string values |
| List<String> leftValues = getStringValues(leftOperand, p); |
| List<String> rightValues = getStringValues(rightOperand, p); |
| |
| boolean nonNullStringConcat = |
| atypeFactory.getChecker().hasOption("nonNullStringsConcatenation"); |
| |
| if (leftValues != null && rightValues != null) { |
| // Both operands have known string values, compute set of results |
| if (!nonNullStringConcat) { |
| if (isNullable(leftOperand)) { |
| leftValues = CollectionsPlume.append(leftValues, "null"); |
| } |
| if (isNullable(rightOperand)) { |
| rightValues = CollectionsPlume.append(rightValues, "null"); |
| } |
| } else { |
| if (leftOperand instanceof StringConversionNode) { |
| if (((StringConversionNode) leftOperand).getOperand().getType().getKind() |
| == TypeKind.NULL) { |
| leftValues = CollectionsPlume.append(leftValues, "null"); |
| } |
| } |
| if (rightOperand instanceof StringConversionNode) { |
| if (((StringConversionNode) rightOperand).getOperand().getType().getKind() |
| == TypeKind.NULL) { |
| rightValues = CollectionsPlume.append(rightValues, "null"); |
| } |
| } |
| } |
| |
| List<String> concatValues = new ArrayList<>(leftValues.size() * rightValues.size()); |
| for (String left : leftValues) { |
| for (String right : rightValues) { |
| concatValues.add(left + right); |
| } |
| } |
| return atypeFactory.createStringAnnotation(concatValues); |
| } |
| |
| // Try using sets of lengths |
| List<Integer> leftLengths = |
| leftValues != null |
| ? ValueCheckerUtils.getLengthsForStringValues(leftValues) |
| : getStringLengths(leftOperand, p); |
| List<Integer> rightLengths = |
| rightValues != null |
| ? ValueCheckerUtils.getLengthsForStringValues(rightValues) |
| : getStringLengths(rightOperand, p); |
| |
| if (leftLengths != null && rightLengths != null) { |
| // Both operands have known lengths, compute set of result lengths |
| if (!nonNullStringConcat) { |
| if (isNullable(leftOperand)) { |
| leftLengths = new ArrayList<>(leftLengths); |
| leftLengths.add(4); // "null" |
| } |
| if (isNullable(rightOperand)) { |
| rightLengths = new ArrayList<>(rightLengths); |
| rightLengths.add(4); // "null" |
| } |
| } |
| List<Integer> concatLengths = calculateLengthAddition(leftLengths, rightLengths); |
| return atypeFactory.createArrayLenAnnotation(concatLengths); |
| } |
| |
| // Try using ranges of lengths |
| Range leftLengthRange = |
| leftLengths != null |
| ? ValueCheckerUtils.getRangeFromValues(leftLengths) |
| : getStringLengthRange(leftOperand, p); |
| Range rightLengthRange = |
| rightLengths != null |
| ? ValueCheckerUtils.getRangeFromValues(rightLengths) |
| : getStringLengthRange(rightOperand, p); |
| |
| if (leftLengthRange != null && rightLengthRange != null) { |
| // Both operands have a length from a known range, compute a range of result lengths |
| if (!nonNullStringConcat) { |
| if (isNullable(leftOperand)) { |
| leftLengthRange.union(Range.create(4, 4)); // "null" |
| } |
| if (isNullable(rightOperand)) { |
| rightLengthRange.union(Range.create(4, 4)); // "null" |
| } |
| } |
| Range concatLengthRange = calculateLengthRangeAddition(leftLengthRange, rightLengthRange); |
| return atypeFactory.createArrayLenRangeAnnotation(concatLengthRange); |
| } |
| |
| return atypeFactory.UNKNOWNVAL; |
| } |
| |
| public TransferResult<CFValue, CFStore> stringConcatenation( |
| Node leftOperand, |
| Node rightOperand, |
| TransferInput<CFValue, CFStore> p, |
| TransferResult<CFValue, CFStore> result) { |
| |
| AnnotationMirror resultAnno = |
| createAnnotationForStringConcatenation(leftOperand, rightOperand, p); |
| |
| TypeMirror underlyingType = result.getResultValue().getUnderlyingType(); |
| CFValue newResultValue = analysis.createSingleAnnotationValue(resultAnno, underlyingType); |
| return new RegularTransferResult<>(newResultValue, result.getRegularStore()); |
| } |
| |
| /** Binary operations that are analyzed by the value checker. */ |
| enum NumericalBinaryOps { |
| ADDITION, |
| SUBTRACTION, |
| DIVISION, |
| REMAINDER, |
| MULTIPLICATION, |
| SHIFT_LEFT, |
| SIGNED_SHIFT_RIGHT, |
| UNSIGNED_SHIFT_RIGHT, |
| BITWISE_AND, |
| BITWISE_OR, |
| BITWISE_XOR; |
| } |
| |
| /** |
| * Get the refined annotation after a numerical binary operation. |
| * |
| * @param leftNode the node that represents the left operand |
| * @param rightNode the node that represents the right operand |
| * @param op the operator type |
| * @param p the transfer input |
| * @return the result annotation mirror |
| */ |
| private AnnotationMirror calculateNumericalBinaryOp( |
| Node leftNode, Node rightNode, NumericalBinaryOps op, TransferInput<CFValue, CFStore> p) { |
| if (!isIntRangeOrIntegralUnknownVal(leftNode, p) |
| && !isIntRangeOrIntegralUnknownVal(rightNode, p)) { |
| List<Number> resultValues = calculateValuesBinaryOp(leftNode, rightNode, op, p); |
| return atypeFactory.createNumberAnnotationMirror(resultValues); |
| } else { |
| Range resultRange = calculateRangeBinaryOp(leftNode, rightNode, op, p); |
| return atypeFactory.createIntRangeAnnotation(resultRange); |
| } |
| } |
| |
| /** Calculate the result range after a binary operation between two numerical type nodes. */ |
| private Range calculateRangeBinaryOp( |
| Node leftNode, Node rightNode, NumericalBinaryOps op, TransferInput<CFValue, CFStore> p) { |
| if (TypesUtils.isIntegralPrimitive(leftNode.getType()) |
| && TypesUtils.isIntegralPrimitive(rightNode.getType())) { |
| Range leftRange = getIntRange(leftNode, p); |
| Range rightRange = getIntRange(rightNode, p); |
| Range resultRange; |
| switch (op) { |
| case ADDITION: |
| resultRange = leftRange.plus(rightRange); |
| break; |
| case SUBTRACTION: |
| resultRange = leftRange.minus(rightRange); |
| break; |
| case MULTIPLICATION: |
| resultRange = leftRange.times(rightRange); |
| break; |
| case DIVISION: |
| resultRange = leftRange.divide(rightRange); |
| break; |
| case REMAINDER: |
| resultRange = leftRange.remainder(rightRange); |
| break; |
| case SHIFT_LEFT: |
| resultRange = leftRange.shiftLeft(rightRange); |
| break; |
| case SIGNED_SHIFT_RIGHT: |
| resultRange = leftRange.signedShiftRight(rightRange); |
| break; |
| case UNSIGNED_SHIFT_RIGHT: |
| resultRange = leftRange.unsignedShiftRight(rightRange); |
| break; |
| case BITWISE_AND: |
| resultRange = leftRange.bitwiseAnd(rightRange); |
| break; |
| case BITWISE_OR: |
| resultRange = leftRange.bitwiseOr(rightRange); |
| break; |
| case BITWISE_XOR: |
| resultRange = leftRange.bitwiseXor(rightRange); |
| break; |
| default: |
| throw new BugInCF("ValueTransfer: unsupported operation: " + op); |
| } |
| // Any integral type with less than 32 bits would be promoted to 32-bit int type during |
| // operations. |
| return leftNode.getType().getKind() == TypeKind.LONG |
| || rightNode.getType().getKind() == TypeKind.LONG |
| ? resultRange |
| : resultRange.intRange(); |
| } else { |
| return Range.EVERYTHING; |
| } |
| } |
| |
| /** Calculate the possible values after a binary operation between two numerical type nodes. */ |
| private List<Number> calculateValuesBinaryOp( |
| Node leftNode, Node rightNode, NumericalBinaryOps op, TransferInput<CFValue, CFStore> p) { |
| List<? extends Number> lefts = getNumericalValues(leftNode, p); |
| List<? extends Number> rights = getNumericalValues(rightNode, p); |
| if (lefts == null || rights == null) { |
| return null; |
| } |
| List<Number> resultValues = new ArrayList<>(); |
| for (Number left : lefts) { |
| NumberMath<?> nmLeft = NumberMath.getNumberMath(left); |
| for (Number right : rights) { |
| switch (op) { |
| case ADDITION: |
| resultValues.add(nmLeft.plus(right)); |
| break; |
| case DIVISION: |
| Number result = nmLeft.divide(right); |
| if (result != null) { |
| resultValues.add(result); |
| } |
| break; |
| case MULTIPLICATION: |
| resultValues.add(nmLeft.times(right)); |
| break; |
| case REMAINDER: |
| Number resultR = nmLeft.remainder(right); |
| if (resultR != null) { |
| resultValues.add(resultR); |
| } |
| break; |
| case SUBTRACTION: |
| resultValues.add(nmLeft.minus(right)); |
| break; |
| case SHIFT_LEFT: |
| resultValues.add(nmLeft.shiftLeft(right)); |
| break; |
| case SIGNED_SHIFT_RIGHT: |
| resultValues.add(nmLeft.signedShiftRight(right)); |
| break; |
| case UNSIGNED_SHIFT_RIGHT: |
| resultValues.add(nmLeft.unsignedShiftRight(right)); |
| break; |
| case BITWISE_AND: |
| resultValues.add(nmLeft.bitwiseAnd(right)); |
| break; |
| case BITWISE_OR: |
| resultValues.add(nmLeft.bitwiseOr(right)); |
| break; |
| case BITWISE_XOR: |
| resultValues.add(nmLeft.bitwiseXor(right)); |
| break; |
| default: |
| throw new BugInCF("ValueTransfer: unsupported operation: " + op); |
| } |
| } |
| } |
| return resultValues; |
| } |
| |
| @Override |
| public TransferResult<CFValue, CFStore> visitNumericalAddition( |
| NumericalAdditionNode n, TransferInput<CFValue, CFStore> p) { |
| TransferResult<CFValue, CFStore> transferResult = super.visitNumericalAddition(n, p); |
| AnnotationMirror resultAnno = |
| calculateNumericalBinaryOp( |
| n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.ADDITION, p); |
| return createNewResult(transferResult, resultAnno); |
| } |
| |
| @Override |
| public TransferResult<CFValue, CFStore> visitNumericalSubtraction( |
| NumericalSubtractionNode n, TransferInput<CFValue, CFStore> p) { |
| TransferResult<CFValue, CFStore> transferResult = super.visitNumericalSubtraction(n, p); |
| AnnotationMirror resultAnno = |
| calculateNumericalBinaryOp( |
| n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.SUBTRACTION, p); |
| return createNewResult(transferResult, resultAnno); |
| } |
| |
| @Override |
| public TransferResult<CFValue, CFStore> visitNumericalMultiplication( |
| NumericalMultiplicationNode n, TransferInput<CFValue, CFStore> p) { |
| TransferResult<CFValue, CFStore> transferResult = super.visitNumericalMultiplication(n, p); |
| AnnotationMirror resultAnno = |
| calculateNumericalBinaryOp( |
| n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.MULTIPLICATION, p); |
| return createNewResult(transferResult, resultAnno); |
| } |
| |
| @Override |
| public TransferResult<CFValue, CFStore> visitIntegerDivision( |
| IntegerDivisionNode n, TransferInput<CFValue, CFStore> p) { |
| TransferResult<CFValue, CFStore> transferResult = super.visitIntegerDivision(n, p); |
| AnnotationMirror resultAnno = |
| calculateNumericalBinaryOp( |
| n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.DIVISION, p); |
| return createNewResult(transferResult, resultAnno); |
| } |
| |
| @Override |
| public TransferResult<CFValue, CFStore> visitFloatingDivision( |
| FloatingDivisionNode n, TransferInput<CFValue, CFStore> p) { |
| TransferResult<CFValue, CFStore> transferResult = super.visitFloatingDivision(n, p); |
| AnnotationMirror resultAnno = |
| calculateNumericalBinaryOp( |
| n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.DIVISION, p); |
| return createNewResult(transferResult, resultAnno); |
| } |
| |
| @Override |
| public TransferResult<CFValue, CFStore> visitIntegerRemainder( |
| IntegerRemainderNode n, TransferInput<CFValue, CFStore> p) { |
| TransferResult<CFValue, CFStore> transferResult = super.visitIntegerRemainder(n, p); |
| AnnotationMirror resultAnno = |
| calculateNumericalBinaryOp( |
| n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.REMAINDER, p); |
| return createNewResult(transferResult, resultAnno); |
| } |
| |
| @Override |
| public TransferResult<CFValue, CFStore> visitFloatingRemainder( |
| FloatingRemainderNode n, TransferInput<CFValue, CFStore> p) { |
| TransferResult<CFValue, CFStore> transferResult = super.visitFloatingRemainder(n, p); |
| AnnotationMirror resultAnno = |
| calculateNumericalBinaryOp( |
| n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.REMAINDER, p); |
| return createNewResult(transferResult, resultAnno); |
| } |
| |
| @Override |
| public TransferResult<CFValue, CFStore> visitLeftShift( |
| LeftShiftNode n, TransferInput<CFValue, CFStore> p) { |
| TransferResult<CFValue, CFStore> transferResult = super.visitLeftShift(n, p); |
| AnnotationMirror resultAnno = |
| calculateNumericalBinaryOp( |
| n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.SHIFT_LEFT, p); |
| return createNewResult(transferResult, resultAnno); |
| } |
| |
| @Override |
| public TransferResult<CFValue, CFStore> visitSignedRightShift( |
| SignedRightShiftNode n, TransferInput<CFValue, CFStore> p) { |
| TransferResult<CFValue, CFStore> transferResult = super.visitSignedRightShift(n, p); |
| AnnotationMirror resultAnno = |
| calculateNumericalBinaryOp( |
| n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.SIGNED_SHIFT_RIGHT, p); |
| return createNewResult(transferResult, resultAnno); |
| } |
| |
| @Override |
| public TransferResult<CFValue, CFStore> visitUnsignedRightShift( |
| UnsignedRightShiftNode n, TransferInput<CFValue, CFStore> p) { |
| TransferResult<CFValue, CFStore> transferResult = super.visitUnsignedRightShift(n, p); |
| AnnotationMirror resultAnno = |
| calculateNumericalBinaryOp( |
| n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.UNSIGNED_SHIFT_RIGHT, p); |
| return createNewResult(transferResult, resultAnno); |
| } |
| |
| @Override |
| public TransferResult<CFValue, CFStore> visitBitwiseAnd( |
| BitwiseAndNode n, TransferInput<CFValue, CFStore> p) { |
| TransferResult<CFValue, CFStore> transferResult = super.visitBitwiseAnd(n, p); |
| AnnotationMirror resultAnno = |
| calculateNumericalBinaryOp( |
| n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.BITWISE_AND, p); |
| return createNewResult(transferResult, resultAnno); |
| } |
| |
| @Override |
| public TransferResult<CFValue, CFStore> visitBitwiseOr( |
| BitwiseOrNode n, TransferInput<CFValue, CFStore> p) { |
| TransferResult<CFValue, CFStore> transferResult = super.visitBitwiseOr(n, p); |
| AnnotationMirror resultAnno = |
| calculateNumericalBinaryOp( |
| n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.BITWISE_OR, p); |
| return createNewResult(transferResult, resultAnno); |
| } |
| |
| @Override |
| public TransferResult<CFValue, CFStore> visitBitwiseXor( |
| BitwiseXorNode n, TransferInput<CFValue, CFStore> p) { |
| TransferResult<CFValue, CFStore> transferResult = super.visitBitwiseXor(n, p); |
| AnnotationMirror resultAnno = |
| calculateNumericalBinaryOp( |
| n.getLeftOperand(), n.getRightOperand(), NumericalBinaryOps.BITWISE_XOR, p); |
| return createNewResult(transferResult, resultAnno); |
| } |
| |
| /** Unary operations that are analyzed by the value checker. */ |
| enum NumericalUnaryOps { |
| PLUS, |
| MINUS, |
| BITWISE_COMPLEMENT; |
| } |
| |
| /** |
| * Get the refined annotation after a numerical unary operation. |
| * |
| * @param operand the node that represents the operand |
| * @param op the operator type |
| * @param p the transfer input |
| * @return the result annotation mirror |
| */ |
| private AnnotationMirror calculateNumericalUnaryOp( |
| Node operand, NumericalUnaryOps op, TransferInput<CFValue, CFStore> p) { |
| if (!isIntRange(operand, p)) { |
| List<Number> resultValues = calculateValuesUnaryOp(operand, op, p); |
| return atypeFactory.createNumberAnnotationMirror(resultValues); |
| } else { |
| Range resultRange = calculateRangeUnaryOp(operand, op, p); |
| return atypeFactory.createIntRangeAnnotation(resultRange); |
| } |
| } |
| |
| /** |
| * Calculate the result range after a unary operation of a numerical type node. |
| * |
| * @param operand the node that represents the operand |
| * @param op the operator type |
| * @param p the transfer input |
| * @return the result annotation mirror |
| */ |
| private Range calculateRangeUnaryOp( |
| Node operand, NumericalUnaryOps op, TransferInput<CFValue, CFStore> p) { |
| if (TypesUtils.isIntegralPrimitive(operand.getType())) { |
| Range range = getIntRange(operand, p); |
| Range resultRange; |
| switch (op) { |
| case PLUS: |
| resultRange = range.unaryPlus(); |
| break; |
| case MINUS: |
| resultRange = range.unaryMinus(); |
| break; |
| case BITWISE_COMPLEMENT: |
| resultRange = range.bitwiseComplement(); |
| break; |
| default: |
| throw new BugInCF("ValueTransfer: unsupported operation: " + op); |
| } |
| // Any integral type with less than 32 bits would be promoted to 32-bit int type during |
| // operations. |
| return operand.getType().getKind() == TypeKind.LONG ? resultRange : resultRange.intRange(); |
| } else { |
| return Range.EVERYTHING; |
| } |
| } |
| |
| /** Calculate the possible values after a unary operation of a numerical type node. */ |
| private List<Number> calculateValuesUnaryOp( |
| Node operand, NumericalUnaryOps op, TransferInput<CFValue, CFStore> p) { |
| List<? extends Number> lefts = getNumericalValues(operand, p); |
| if (lefts == null) { |
| return null; |
| } |
| List<Number> resultValues = new ArrayList<>(lefts.size()); |
| for (Number left : lefts) { |
| NumberMath<?> nmLeft = NumberMath.getNumberMath(left); |
| switch (op) { |
| case PLUS: |
| resultValues.add(nmLeft.unaryPlus()); |
| break; |
| case MINUS: |
| resultValues.add(nmLeft.unaryMinus()); |
| break; |
| case BITWISE_COMPLEMENT: |
| resultValues.add(nmLeft.bitwiseComplement()); |
| break; |
| default: |
| throw new BugInCF("ValueTransfer: unsupported operation: " + op); |
| } |
| } |
| return resultValues; |
| } |
| |
| @Override |
| public TransferResult<CFValue, CFStore> visitNumericalMinus( |
| NumericalMinusNode n, TransferInput<CFValue, CFStore> p) { |
| TransferResult<CFValue, CFStore> transferResult = super.visitNumericalMinus(n, p); |
| AnnotationMirror resultAnno = |
| calculateNumericalUnaryOp(n.getOperand(), NumericalUnaryOps.MINUS, p); |
| return createNewResult(transferResult, resultAnno); |
| } |
| |
| @Override |
| public TransferResult<CFValue, CFStore> visitNumericalPlus( |
| NumericalPlusNode n, TransferInput<CFValue, CFStore> p) { |
| TransferResult<CFValue, CFStore> transferResult = super.visitNumericalPlus(n, p); |
| AnnotationMirror resultAnno = |
| calculateNumericalUnaryOp(n.getOperand(), NumericalUnaryOps.PLUS, p); |
| return createNewResult(transferResult, resultAnno); |
| } |
| |
| @Override |
| public TransferResult<CFValue, CFStore> visitBitwiseComplement( |
| BitwiseComplementNode n, TransferInput<CFValue, CFStore> p) { |
| TransferResult<CFValue, CFStore> transferResult = super.visitBitwiseComplement(n, p); |
| AnnotationMirror resultAnno = |
| calculateNumericalUnaryOp(n.getOperand(), NumericalUnaryOps.BITWISE_COMPLEMENT, p); |
| return createNewResult(transferResult, resultAnno); |
| } |
| |
| enum ComparisonOperators { |
| EQUAL, |
| NOT_EQUAL, |
| GREATER_THAN, |
| GREATER_THAN_EQ, |
| LESS_THAN, |
| LESS_THAN_EQ; |
| } |
| |
| private List<Boolean> calculateBinaryComparison( |
| Node leftNode, |
| CFValue leftValue, |
| Node rightNode, |
| CFValue rightValue, |
| ComparisonOperators op, |
| CFStore thenStore, |
| CFStore elseStore) { |
| AnnotationMirror leftAnno = getValueAnnotation(leftValue); |
| AnnotationMirror rightAnno = getValueAnnotation(rightValue); |
| |
| if (atypeFactory.isIntRange(leftAnno) |
| || atypeFactory.isIntRange(rightAnno) |
| || isIntegralUnknownVal(rightNode, rightAnno) |
| || isIntegralUnknownVal(leftNode, leftAnno)) { |
| // If either is @UnknownVal, then refineIntRanges will treat it as the max range and thus |
| // refine it if possible. Also, if either is an @IntVal, then it will be converted to a |
| // range. This is less precise in some cases, but avoids the complexity of comparing a list |
| // of values to a range. (This could be implemented in the future.) |
| return refineIntRanges(leftNode, leftAnno, rightNode, rightAnno, op, thenStore, elseStore); |
| } |
| |
| List<? extends Number> lefts = getNumericalValues(leftNode, leftAnno); |
| List<? extends Number> rights = getNumericalValues(rightNode, rightAnno); |
| |
| if (lefts == null || rights == null) { |
| // Appropriately handle bottom when something is compared to bottom. |
| if (AnnotationUtils.areSame(leftAnno, atypeFactory.BOTTOMVAL) |
| || AnnotationUtils.areSame(rightAnno, atypeFactory.BOTTOMVAL)) { |
| return Collections.emptyList(); |
| } |
| return null; |
| } |
| |
| // This is a list of all the values that the expression can evaluate to. |
| List<Boolean> resultValues = new ArrayList<>(); |
| |
| // These lists are used to refine the values in the store based on the results of the |
| // comparison. |
| List<Number> thenLeftVals = new ArrayList<>(); |
| List<Number> elseLeftVals = new ArrayList<>(); |
| List<Number> thenRightVals = new ArrayList<>(); |
| List<Number> elseRightVals = new ArrayList<>(); |
| |
| for (Number left : lefts) { |
| NumberMath<?> nmLeft = NumberMath.getNumberMath(left); |
| for (Number right : rights) { |
| Boolean result; |
| switch (op) { |
| case EQUAL: |
| result = nmLeft.equalTo(right); |
| break; |
| case GREATER_THAN: |
| result = nmLeft.greaterThan(right); |
| break; |
| case GREATER_THAN_EQ: |
| result = nmLeft.greaterThanEq(right); |
| break; |
| case LESS_THAN: |
| result = nmLeft.lessThan(right); |
| break; |
| case LESS_THAN_EQ: |
| result = nmLeft.lessThanEq(right); |
| break; |
| case NOT_EQUAL: |
| result = nmLeft.notEqualTo(right); |
| break; |
| default: |
| throw new BugInCF("ValueTransfer: unsupported operation: " + op); |
| } |
| resultValues.add(result); |
| if (result) { |
| thenLeftVals.add(left); |
| thenRightVals.add(right); |
| } else { |
| elseLeftVals.add(left); |
| elseRightVals.add(right); |
| } |
| } |
| } |
| |
| createAnnotationFromResultsAndAddToStore(thenStore, thenLeftVals, leftNode); |
| createAnnotationFromResultsAndAddToStore(elseStore, elseLeftVals, leftNode); |
| createAnnotationFromResultsAndAddToStore(thenStore, thenRightVals, rightNode); |
| createAnnotationFromResultsAndAddToStore(elseStore, elseRightVals, rightNode); |
| |
| return resultValues; |
| } |
| |
| /** |
| * Calculates the result of a binary comparison on a pair of intRange annotations, and refines |
| * annotations appropriately. |
| */ |
| private List<Boolean> refineIntRanges( |
| Node leftNode, |
| AnnotationMirror leftAnno, |
| Node rightNode, |
| AnnotationMirror rightAnno, |
| ComparisonOperators op, |
| CFStore thenStore, |
| CFStore elseStore) { |
| |
| Range leftRange = getIntRangeFromAnnotation(leftNode, leftAnno); |
| Range rightRange = getIntRangeFromAnnotation(rightNode, rightAnno); |
| |
| final Range thenRightRange; |
| final Range thenLeftRange; |
| final Range elseRightRange; |
| final Range elseLeftRange; |
| |
| switch (op) { |
| case EQUAL: |
| thenRightRange = rightRange.refineEqualTo(leftRange); |
| thenLeftRange = thenRightRange; // Only needs to be computed once. |
| elseRightRange = rightRange.refineNotEqualTo(leftRange); |
| elseLeftRange = leftRange.refineNotEqualTo(rightRange); |
| break; |
| case GREATER_THAN: |
| thenLeftRange = leftRange.refineGreaterThan(rightRange); |
| thenRightRange = rightRange.refineLessThan(leftRange); |
| elseRightRange = rightRange.refineGreaterThanEq(leftRange); |
| elseLeftRange = leftRange.refineLessThanEq(rightRange); |
| break; |
| case GREATER_THAN_EQ: |
| thenRightRange = rightRange.refineLessThanEq(leftRange); |
| thenLeftRange = leftRange.refineGreaterThanEq(rightRange); |
| elseLeftRange = leftRange.refineLessThan(rightRange); |
| elseRightRange = rightRange.refineGreaterThan(leftRange); |
| break; |
| case LESS_THAN: |
| thenLeftRange = leftRange.refineLessThan(rightRange); |
| thenRightRange = rightRange.refineGreaterThan(leftRange); |
| elseRightRange = rightRange.refineLessThanEq(leftRange); |
| elseLeftRange = leftRange.refineGreaterThanEq(rightRange); |
| break; |
| case LESS_THAN_EQ: |
| thenRightRange = rightRange.refineGreaterThanEq(leftRange); |
| thenLeftRange = leftRange.refineLessThanEq(rightRange); |
| elseLeftRange = leftRange.refineGreaterThan(rightRange); |
| elseRightRange = rightRange.refineLessThan(leftRange); |
| break; |
| case NOT_EQUAL: |
| thenRightRange = rightRange.refineNotEqualTo(leftRange); |
| thenLeftRange = leftRange.refineNotEqualTo(rightRange); |
| elseRightRange = rightRange.refineEqualTo(leftRange); |
| elseLeftRange = elseRightRange; // Equality only needs to be computed once. |
| break; |
| default: |
| throw new BugInCF("ValueTransfer: unsupported operation: " + op); |
| } |
| |
| createAnnotationFromRangeAndAddToStore(thenStore, thenRightRange, rightNode); |
| createAnnotationFromRangeAndAddToStore(thenStore, thenLeftRange, leftNode); |
| createAnnotationFromRangeAndAddToStore(elseStore, elseRightRange, rightNode); |
| createAnnotationFromRangeAndAddToStore(elseStore, elseLeftRange, leftNode); |
| |
| // TODO: Refine the type of the comparison. |
| return null; |
| } |
| |
| /** |
| * Takes a list of result values (i.e. the values possible after the comparison) and creates the |
| * appropriate annotation from them, then combines that annotation with the existing annotation on |
| * the node. The resulting annotation is inserted into the store. |
| * |
| * @param store the store |
| * @param results the result values |
| * @param node the node whose existing annotation to refine |
| */ |
| private void createAnnotationFromResultsAndAddToStore(CFStore store, List<?> results, Node node) { |
| AnnotationMirror anno = atypeFactory.createResultingAnnotation(node.getType(), results); |
| addAnnotationToStore(store, anno, node); |
| } |
| |
| /** |
| * Takes a range and creates the appropriate annotation from it, then combines that annotation |
| * with the existing annotation on the node. The resulting annotation is inserted into the store. |
| * |
| * @param store the store |
| * @param range the range to create an annotation for |
| * @param node the node whose existing annotation to refine |
| */ |
| private void createAnnotationFromRangeAndAddToStore(CFStore store, Range range, Node node) { |
| AnnotationMirror anno = atypeFactory.createIntRangeAnnotation(range); |
| addAnnotationToStore(store, anno, node); |
| } |
| |
| private void addAnnotationToStore(CFStore store, AnnotationMirror anno, Node node) { |
| // If node is assignment, iterate over lhs and rhs; otherwise, iterator contains just node. |
| for (Node internal : splitAssignments(node)) { |
| JavaExpression je = JavaExpression.fromNode(internal); |
| CFValue currentValueFromStore; |
| if (CFAbstractStore.canInsertJavaExpression(je)) { |
| currentValueFromStore = store.getValue(je); |
| } else { |
| // Don't just `continue;` which would skip the calls to refine{Array,String}... |
| currentValueFromStore = null; |
| } |
| AnnotationMirror currentAnno = |
| (currentValueFromStore == null |
| ? atypeFactory.UNKNOWNVAL |
| : getValueAnnotation(currentValueFromStore)); |
| // Combine the new annotations based on the results of the comparison with the existing type. |
| AnnotationMirror newAnno = hierarchy.greatestLowerBound(anno, currentAnno); |
| store.insertValue(je, newAnno); |
| |
| if (node instanceof FieldAccessNode) { |
| refineArrayAtLengthAccess((FieldAccessNode) internal, store); |
| } else if (node instanceof MethodInvocationNode) { |
| MethodInvocationNode miNode = (MethodInvocationNode) node; |
| refineAtLengthInvocation(miNode, store); |
| } |
| } |
| } |
| |
| @Override |
| public TransferResult<CFValue, CFStore> visitLessThan( |
| LessThanNode n, TransferInput<CFValue, CFStore> p) { |
| TransferResult<CFValue, CFStore> transferResult = super.visitLessThan(n, p); |
| CFStore thenStore = transferResult.getThenStore(); |
| CFStore elseStore = transferResult.getElseStore(); |
| List<Boolean> resultValues = |
| calculateBinaryComparison( |
| n.getLeftOperand(), |
| p.getValueOfSubNode(n.getLeftOperand()), |
| n.getRightOperand(), |
| p.getValueOfSubNode(n.getRightOperand()), |
| ComparisonOperators.LESS_THAN, |
| thenStore, |
| elseStore); |
| TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType(); |
| return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType); |
| } |
| |
| @Override |
| public TransferResult<CFValue, CFStore> visitLessThanOrEqual( |
| LessThanOrEqualNode n, TransferInput<CFValue, CFStore> p) { |
| TransferResult<CFValue, CFStore> transferResult = super.visitLessThanOrEqual(n, p); |
| CFStore thenStore = transferResult.getThenStore(); |
| CFStore elseStore = transferResult.getElseStore(); |
| List<Boolean> resultValues = |
| calculateBinaryComparison( |
| n.getLeftOperand(), |
| p.getValueOfSubNode(n.getLeftOperand()), |
| n.getRightOperand(), |
| p.getValueOfSubNode(n.getRightOperand()), |
| ComparisonOperators.LESS_THAN_EQ, |
| thenStore, |
| elseStore); |
| TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType(); |
| return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType); |
| } |
| |
| @Override |
| public TransferResult<CFValue, CFStore> visitGreaterThan( |
| GreaterThanNode n, TransferInput<CFValue, CFStore> p) { |
| TransferResult<CFValue, CFStore> transferResult = super.visitGreaterThan(n, p); |
| CFStore thenStore = transferResult.getThenStore(); |
| CFStore elseStore = transferResult.getElseStore(); |
| List<Boolean> resultValues = |
| calculateBinaryComparison( |
| n.getLeftOperand(), |
| p.getValueOfSubNode(n.getLeftOperand()), |
| n.getRightOperand(), |
| p.getValueOfSubNode(n.getRightOperand()), |
| ComparisonOperators.GREATER_THAN, |
| thenStore, |
| elseStore); |
| TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType(); |
| return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType); |
| } |
| |
| @Override |
| public TransferResult<CFValue, CFStore> visitGreaterThanOrEqual( |
| GreaterThanOrEqualNode n, TransferInput<CFValue, CFStore> p) { |
| TransferResult<CFValue, CFStore> transferResult = super.visitGreaterThanOrEqual(n, p); |
| CFStore thenStore = transferResult.getThenStore(); |
| CFStore elseStore = transferResult.getElseStore(); |
| List<Boolean> resultValues = |
| calculateBinaryComparison( |
| n.getLeftOperand(), |
| p.getValueOfSubNode(n.getLeftOperand()), |
| n.getRightOperand(), |
| p.getValueOfSubNode(n.getRightOperand()), |
| ComparisonOperators.GREATER_THAN_EQ, |
| thenStore, |
| elseStore); |
| TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType(); |
| return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType); |
| } |
| |
| @Override |
| protected TransferResult<CFValue, CFStore> strengthenAnnotationOfEqualTo( |
| TransferResult<CFValue, CFStore> transferResult, |
| Node firstNode, |
| Node secondNode, |
| CFValue firstValue, |
| CFValue secondValue, |
| boolean notEqualTo) { |
| if (firstValue == null) { |
| return transferResult; |
| } |
| if (TypesUtils.isNumeric(firstNode.getType()) || TypesUtils.isNumeric(secondNode.getType())) { |
| CFStore thenStore = transferResult.getThenStore(); |
| CFStore elseStore = transferResult.getElseStore(); |
| // At least one must be a primitive otherwise reference equality is used. |
| List<Boolean> resultValues = |
| calculateBinaryComparison( |
| firstNode, |
| firstValue, |
| secondNode, |
| secondValue, |
| notEqualTo ? ComparisonOperators.NOT_EQUAL : ComparisonOperators.EQUAL, |
| thenStore, |
| elseStore); |
| if (transferResult.getResultValue() == null) { |
| // Happens for case labels |
| return transferResult; |
| } |
| TypeMirror underlyingType = transferResult.getResultValue().getUnderlyingType(); |
| return createNewResultBoolean(thenStore, elseStore, resultValues, underlyingType); |
| } |
| return super.strengthenAnnotationOfEqualTo( |
| transferResult, firstNode, secondNode, firstValue, secondValue, notEqualTo); |
| } |
| |
| @Override |
| protected void processConditionalPostconditions( |
| MethodInvocationNode n, |
| ExecutableElement methodElement, |
| Tree tree, |
| CFStore thenStore, |
| CFStore elseStore) { |
| // For String.startsWith(String) and String.endsWith(String), refine the minimum length |
| // of the receiver to the minimum length of the argument. |
| |
| ValueMethodIdentifier methodIdentifier = atypeFactory.getMethodIdentifier(); |
| if (methodIdentifier.isStartsWithMethod(methodElement) |
| || methodIdentifier.isEndsWithMethod(methodElement)) { |
| |
| Node argumentNode = n.getArgument(0); |
| AnnotationMirror argumentAnno = getArrayOrStringAnnotation(argumentNode); |
| int minLength = atypeFactory.getMinLenValue(argumentAnno); |
| // Update the annotation of the receiver |
| if (minLength != 0) { |
| JavaExpression receiver = JavaExpression.fromNode(n.getTarget().getReceiver()); |
| |
| AnnotationMirror minLenAnno = |
| atypeFactory.createArrayLenRangeAnnotation(minLength, Integer.MAX_VALUE); |
| thenStore.insertValuePermitNondeterministic(receiver, minLenAnno); |
| } |
| } |
| |
| super.processConditionalPostconditions(n, methodElement, tree, thenStore, elseStore); |
| } |
| |
| enum ConditionalOperators { |
| NOT, |
| OR, |
| AND; |
| } |
| |
| private static final List<Boolean> ALL_BOOLEANS = |
| Arrays.asList(new Boolean[] {Boolean.TRUE, Boolean.FALSE}); |
| |
| private List<Boolean> calculateConditionalOperator( |
| Node leftNode, Node rightNode, ConditionalOperators op, TransferInput<CFValue, CFStore> p) { |
| List<Boolean> lefts = getBooleanValues(leftNode, p); |
| if (lefts == null) { |
| lefts = ALL_BOOLEANS; |
| } |
| List<Boolean> rights = null; |
| if (rightNode != null) { |
| rights = getBooleanValues(rightNode, p); |
| if (rights == null) { |
| rights = ALL_BOOLEANS; |
| } |
| } |
| // This list can contain duplicates. It is deduplicated later by createBooleanAnnotation. |
| List<Boolean> resultValues = new ArrayList<>(2); |
| switch (op) { |
| case NOT: |
| return CollectionsPlume.mapList((Boolean left) -> !left, lefts); |
| case OR: |
| for (Boolean left : lefts) { |
| for (Boolean right : rights) { |
| resultValues.add(left || right); |
| } |
| } |
| return resultValues; |
| case AND: |
| for (Boolean left : lefts) { |
| for (Boolean right : rights) { |
| resultValues.add(left && right); |
| } |
| } |
| return resultValues; |
| } |
| throw new BugInCF("ValueTransfer: unsupported operation: " + op); |
| } |
| |
| @Override |
| public TransferResult<CFValue, CFStore> visitEqualTo( |
| EqualToNode n, TransferInput<CFValue, CFStore> p) { |
| TransferResult<CFValue, CFStore> res = super.visitEqualTo(n, p); |
| |
| Node leftN = n.getLeftOperand(); |
| Node rightN = n.getRightOperand(); |
| CFValue leftV = p.getValueOfSubNode(leftN); |
| CFValue rightV = p.getValueOfSubNode(rightN); |
| |
| // if annotations differ, use the one that is more precise for both |
| // sides (and add it to the store if possible) |
| res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, rightV, false); |
| res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, leftV, false); |
| |
| Boolean leftBoolean = getBooleanValue(leftV); |
| if (leftBoolean != null) { |
| CFValue notLeftV = createBooleanCFValue(!leftBoolean); |
| res = strengthenAnnotationOfEqualTo(res, leftN, rightN, notLeftV, rightV, true); |
| res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, notLeftV, true); |
| } |
| Boolean rightBoolean = getBooleanValue(rightV); |
| if (rightBoolean != null) { |
| CFValue notRightV = createBooleanCFValue(!rightBoolean); |
| res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, notRightV, true); |
| res = strengthenAnnotationOfEqualTo(res, rightN, leftN, notRightV, leftV, true); |
| } |
| |
| return res; |
| } |
| |
| @Override |
| public TransferResult<CFValue, CFStore> visitNotEqual( |
| NotEqualNode n, TransferInput<CFValue, CFStore> p) { |
| TransferResult<CFValue, CFStore> res = super.visitNotEqual(n, p); |
| |
| Node leftN = n.getLeftOperand(); |
| Node rightN = n.getRightOperand(); |
| CFValue leftV = p.getValueOfSubNode(leftN); |
| CFValue rightV = p.getValueOfSubNode(rightN); |
| |
| // if annotations differ, use the one that is more precise for both |
| // sides (and add it to the store if possible) |
| res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, rightV, true); |
| res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, leftV, true); |
| |
| Boolean leftBoolean = getBooleanValue(leftV); |
| if (leftBoolean != null) { |
| CFValue notLeftV = createBooleanCFValue(!leftBoolean); |
| res = strengthenAnnotationOfEqualTo(res, leftN, rightN, notLeftV, rightV, false); |
| res = strengthenAnnotationOfEqualTo(res, rightN, leftN, rightV, notLeftV, false); |
| } |
| Boolean rightBoolean = getBooleanValue(rightV); |
| if (rightBoolean != null) { |
| CFValue notRightV = createBooleanCFValue(!rightBoolean); |
| res = strengthenAnnotationOfEqualTo(res, leftN, rightN, leftV, notRightV, false); |
| res = strengthenAnnotationOfEqualTo(res, rightN, leftN, notRightV, leftV, false); |
| } |
| |
| return res; |
| } |
| |
| @Override |
| public TransferResult<CFValue, CFStore> visitConditionalNot( |
| ConditionalNotNode n, TransferInput<CFValue, CFStore> p) { |
| TransferResult<CFValue, CFStore> transferResult = super.visitConditionalNot(n, p); |
| List<Boolean> resultValues = |
| calculateConditionalOperator(n.getOperand(), null, ConditionalOperators.NOT, p); |
| return createNewResultBoolean( |
| transferResult.getThenStore(), |
| transferResult.getElseStore(), |
| resultValues, |
| transferResult.getResultValue().getUnderlyingType()); |
| } |
| |
| @Override |
| public TransferResult<CFValue, CFStore> visitConditionalAnd( |
| ConditionalAndNode n, TransferInput<CFValue, CFStore> p) { |
| TransferResult<CFValue, CFStore> transferResult = super.visitConditionalAnd(n, p); |
| List<Boolean> resultValues = |
| calculateConditionalOperator( |
| n.getLeftOperand(), n.getRightOperand(), ConditionalOperators.AND, p); |
| return createNewResultBoolean( |
| transferResult.getThenStore(), |
| transferResult.getElseStore(), |
| resultValues, |
| transferResult.getResultValue().getUnderlyingType()); |
| } |
| |
| @Override |
| public TransferResult<CFValue, CFStore> visitConditionalOr( |
| ConditionalOrNode n, TransferInput<CFValue, CFStore> p) { |
| TransferResult<CFValue, CFStore> transferResult = super.visitConditionalOr(n, p); |
| List<Boolean> resultValues = |
| calculateConditionalOperator( |
| n.getLeftOperand(), n.getRightOperand(), ConditionalOperators.OR, p); |
| return createNewResultBoolean( |
| transferResult.getThenStore(), |
| transferResult.getElseStore(), |
| resultValues, |
| transferResult.getResultValue().getUnderlyingType()); |
| } |
| } |