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
40 changes: 40 additions & 0 deletions checker/tests/index/Issue3224.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// 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 {
static class Arrays {
static String[] copyOf(String[] args, int length) {
return args;
}
}

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

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

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

public static void m4(String @MinLen(1) [] args, @IntRange(from = 10, to = 200) int len) {
String @MinLen(1) [] args2 = java.util.Arrays.copyOf(args, len);
}

public static void m5(String @MinLen(1) [] args, String[] otherArray) {
// :: error: assignment.type.incompatible
String @MinLen(1) [] args2 = java.util.Arrays.copyOf(args, otherArray.length);
}

public static void m6(String @MinLen(1) [] args) {
// :: error: assignment.type.incompatible
String @MinLen(1) [] args2 = Arrays.copyOf(args, args.length);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,21 @@ class ValueMethodIdentifier {
private final List<ExecutableElement> mathMinMethod;
/** The {@code java.lang.Math#max()} methods. */
private final List<ExecutableElement> mathMaxMethod;
/** Arrays.copyOf() methods. */
private final List<ExecutableElement> copyOfMethod;

/**
* Initialize elements with methods that have special handling in the value checker
*
* @param processingEnv the processing environment
*/
public ValueMethodIdentifier(ProcessingEnvironment processingEnv) {
lengthMethod = TreeUtils.getMethod("java.lang.String", "length", 0, processingEnv);
startsWithMethod = TreeUtils.getMethod("java.lang.String", "startsWith", 1, processingEnv);
endsWithMethod = TreeUtils.getMethod("java.lang.String", "endsWith", 1, processingEnv);
mathMinMethod = TreeUtils.getMethods("java.lang.Math", "min", 2, processingEnv);
mathMaxMethod = TreeUtils.getMethods("java.lang.Math", "max", 2, processingEnv);
copyOfMethod = TreeUtils.getMethods("java.util.Arrays", "copyOf", 2, processingEnv);
}

/** Returns true iff the argument is an invocation of Math.min. */
Expand Down Expand Up @@ -59,4 +67,15 @@ public boolean isEndsWithMethod(ExecutableElement method) {
// equals (rather than ElementUtils.ismethod) because String.length cannot be overridden
return method.equals(endsWithMethod);
}

/**
* Determines whether a tree is an invocation of the {@code Arrays.copyOf()} method.
*
* @param tree tree to check
* @param processingEnv the processing environment
* @return true iff the argument is an invocation of {@code Arrays.copyOf()} method.
*/
public boolean isArraysCopyOfInvocation(Tree tree, ProcessingEnvironment processingEnv) {
return TreeUtils.isMethodInvocation(tree, copyOfMethod, processingEnv);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
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;
Expand Down Expand Up @@ -398,6 +399,22 @@ public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror
}
}

if (atypeFactory
.getMethodIdentifier()
.isArraysCopyOfInvocation(tree, atypeFactory.getProcessingEnv())) {
List<? extends ExpressionTree> args = tree.getArguments();
if (args.size() != 2) {
throw new BugInCF(
"Arrays.copyOf() should have 2 arguments. This point should not have reached");
}
Range range =
Copy link
Contributor

Choose a reason for hiding this comment

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

range can be null here, but createArrayLenRange assumes its input in non-null. Can you add a null check and only do the type.replaceAnnotation(atypeFactory.createArrayLenRangeAnnotation(range)); if the range is non-null?

ValueCheckerUtils.getPossibleValues(
atypeFactory.getAnnotatedType(args.get(1)), atypeFactory);
if (range != null) {
type.replaceAnnotation(atypeFactory.createArrayLenRangeAnnotation(range));
}
}

if (!methodIsStaticallyExecutable(TreeUtils.elementFromUse(tree))
|| !handledByValueChecker(type)) {
return null;
Expand Down