Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Propagate length information from argument to return value of Arrays.copyOf() #3524

Merged
merged 14 commits into from
Aug 3, 2020
25 changes: 25 additions & 0 deletions checker/tests/index/Issue3224.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Test case for https://tinyurl.com/cfissue/3224

import java.util.Arrays;
import org.checkerframework.common.value.qual.IntRange;
import org.checkerframework.common.value.qual.MinLen;

public class Issue3224 {

public static void m1(String @MinLen(1) [] args) {
int i = 4;
String @MinLen(1) [] args2 = Arrays.copyOf(args, i);
}

public static void m2(String @MinLen(1) [] args) {
String @MinLen(1) [] args2 = Arrays.copyOf(args, args.length);
}

public static void m3(String @MinLen(1) ... args) {
String @MinLen(1) [] args2 = Arrays.copyOf(args, args.length);
}

public static void m4(String @MinLen(1) [] args, @IntRange(from = 10, to = 200) int len) {
String @MinLen(1) [] args2 = Arrays.copyOf(args, len);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,25 @@

import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.TypeCastTree;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.common.basetype.BaseTypeVisitor;
import org.checkerframework.common.value.qual.IntRange;
import org.checkerframework.common.value.qual.IntRangeFromGTENegativeOne;
import org.checkerframework.common.value.qual.IntRangeFromNonNegative;
import org.checkerframework.common.value.qual.IntRangeFromPositive;
import org.checkerframework.common.value.qual.IntVal;
import org.checkerframework.common.value.util.NumberUtils;
import org.checkerframework.common.value.util.Range;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
Expand Down Expand Up @@ -76,6 +80,32 @@ protected void commonAssignmentCheck(
getTypeFactory().createIntRangeAnnotation(Range.CHAR_EVERYTHING));
}

if (valueTree.getKind() == Kind.METHOD_INVOCATION
&& TreeUtils.isArrayscopyOfMethodInvocation((MethodInvocationTree) valueTree)
&& valueType.getKind() == TypeKind.ARRAY) {
List<? extends ExpressionTree> args = ((MethodInvocationTree) valueTree).getArguments();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a check here that the length of args is what you expect (i.e. 2) and throw a BugInCF otherwise

if (getTypeFactory().getAnnotatedType(args.get(1)).getAnnotation(IntVal.class)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than checking for IntVal and IntRange by hand, I suggest using ValueCheckerUtils.getPossibleValues, which returns a Range or null if the annotation isn't integral. It already has support for both IntVal and IntRange, and you should be able to just use its lower bound directly rather than doing the computations below. That will greatly simplify the code.

!= null) {
kelloggm marked this conversation as resolved.
Show resolved Hide resolved
AnnotationMirror argType =
getTypeFactory().getAnnotatedType(args.get(1)).getAnnotation(IntVal.class);
valueType.addAnnotation(
getTypeFactory()
.createArrayLenAnnotation(
ValueAnnotatedTypeFactory.getIntValues(argType).stream()
.map(Long::intValue)
.collect(Collectors.toList())));
}
if (getTypeFactory().getAnnotatedType(args.get(1)).getAnnotation(IntRange.class)
!= null) {
AnnotationMirror argType =
getTypeFactory()
.getAnnotatedType(args.get(1))
.getAnnotation(IntRange.class);
Range range = ValueAnnotatedTypeFactory.getRange(argType);
valueType.addAnnotation(getTypeFactory().createArrayLenRangeAnnotation(range));
}
}

super.commonAssignmentCheck(varType, valueType, valueTree, errorKey, extraArgs);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1273,6 +1273,27 @@ public static boolean isAnonymousConstructor(final MethodTree method) {
return false;
}

/**
* Determines if the tree is a call to Arrays.copyOf()
*
* @param tree tree to check
* @return true if the given tree is a call to Arrays.copyOf() method
*/
public static boolean isArrayscopyOfMethodInvocation(MethodInvocationTree tree) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: in camel-case, this should be isArraysCopyOfMethodInvocation (that is, capitalize the "c" in "copy")

if (tree.getMethodSelect().getKind() != Kind.MEMBER_SELECT) {
return false;
}

MemberSelectTree memberSelectTree = (MemberSelectTree) tree.getMethodSelect();

if (memberSelectTree.getExpression().toString().equals("Arrays")
&& getMethodName(memberSelectTree).equals("copyOf")) {
return true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test isn't strong enough - it will return true on any method named copyOf in a class named Arrays, even if it is not the one from the JDK.

You should look at how IndexMethodIdentifier in the Index Checker identifies specific methods; you should be able to use the same kind of code here to identify Arrays.copyOf with confidence.

(It would probably also be worthwhile to add an inner static class named "Arrays" with a static "copyOf" method to the tests and then ensure that your code is not invoked when that method is called.)

}

return false;
}

/**
* Converts the given AnnotationTrees to AnnotationMirrors.
*
Expand Down