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
Conversation
Update fork
Updating fork
Update fork
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this pull request :)
I think there are some things we can do to improve it. I noted two specific changes in my review, but I'm also somewhat unsure of the algorithm and want to discuss it.
Arrays.copyOf
is guaranteed to return an array of length equal to its second argument, even if the input array is not that long: all of the various versions of it have text like this: "Copies the specified array, truncating or padding with nulls (if necessary) so the copy has the specified length." I think that this means that we don't need a special-case for when the second argument is the length of an array; in all cases, we should convert any IntVal
annotations on the second argument to ArrayLen
annotations on the result (and similarly with IntRange
to ArrayLenRange
. Because the length of an array with an ArrayLen
annotation should have a corresponding IntVal
annotation, the special-case should be taken care of by the other rules in the type system.
&& valueType.getKind() == TypeKind.ARRAY) { | ||
List<? extends ExpressionTree> args = ((MethodInvocationTree) valueTree).getArguments(); | ||
AnnotatedTypeMirror arrType = atypeFactory.getAnnotatedType(args.get(0)); | ||
if (TreeUtils.isArrayLengthAccess(args.get(1))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This only tests that the second argument is any array length access expression. The correct test needs to check that it is an array length access for the array that is the first argument. You can add a test case for this:
public static void m4(String @MinLen(1) [] args, String[] otherArray) {
// :: error: assignment.type.incompatible
String @MinLen(1) [] args2 = Arrays.copyOf(args, otherArray.length);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to check this now? As suggested above I have added to valuetype
any @IntVal
or @IntRange
annotation on the second argument. Suppose the user intends to have args2
of the same length as otherArray
, then this check might lead to a false positive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the test case I suggested, that is a true positive: otherArray
might not be @MinLen(1)
, even if the user hopes that it is - its length could be zero. I think you can add that test method verbatim into the existing test (except for changing the name).
framework/src/main/java/org/checkerframework/common/value/ValueVisitor.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the improvements. I noticed a few other things we can improve, but I think we're getting close.
* @param tree tree to check | ||
* @return true if the given tree is a call to Arrays.copyOf() method | ||
*/ | ||
public static boolean isArrayscopyOfMethodInvocation(MethodInvocationTree tree) { |
There was a problem hiding this comment.
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 (memberSelectTree.getExpression().toString().equals("Arrays") | ||
&& getMethodName(memberSelectTree).equals("copyOf")) { | ||
return true; |
There was a problem hiding this comment.
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.)
if (valueTree.getKind() == Kind.METHOD_INVOCATION | ||
&& TreeUtils.isArrayscopyOfMethodInvocation((MethodInvocationTree) valueTree) | ||
&& valueType.getKind() == TypeKind.ARRAY) { | ||
List<? extends ExpressionTree> args = ((MethodInvocationTree) valueTree).getArguments(); |
There was a problem hiding this comment.
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
&& TreeUtils.isArrayscopyOfMethodInvocation((MethodInvocationTree) valueTree) | ||
&& valueType.getKind() == TypeKind.ARRAY) { | ||
List<? extends ExpressionTree> args = ((MethodInvocationTree) valueTree).getArguments(); | ||
if (getTypeFactory().getAnnotatedType(args.get(1)).getAnnotation(IntVal.class) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One minor comment, no need for me to review again. This can be merged once tests are passing.
Thanks for this contribution - it looks fantastic now!
throw new BugInCF( | ||
"Arrays.copyOf() should have 2 arguments. This point should not have reached"); | ||
} | ||
Range range = |
There was a problem hiding this comment.
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?
Fixes #3224;
The Value checker did not infer array length information from the arguments of
Arrays.copyOf()
method leading to false positives.To avoid those false positives, check if the expression
valueTree
is a call toArrays.copyOf()
method incommonAssignmentCheck()
ofValueVisitor.java
and then add appropriate@ArrayLen
annotations to the return value ofArrays.copyOf()
.