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

Conversation

PRITI1999
Copy link
Contributor

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 to Arrays.copyOf() method in commonAssignmentCheck() of ValueVisitor.java and then add appropriate @ArrayLen annotations to the return value of Arrays.copyOf().

Copy link
Contributor

@kelloggm kelloggm left a 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))) {
Copy link
Contributor

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);
    }

Copy link
Contributor Author

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.

Copy link
Contributor

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).

@PRITI1999 PRITI1999 requested a review from kelloggm July 30, 2020 04:10
Copy link
Contributor

@kelloggm kelloggm left a 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) {
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 (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.)

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

&& TreeUtils.isArrayscopyOfMethodInvocation((MethodInvocationTree) valueTree)
&& valueType.getKind() == TypeKind.ARRAY) {
List<? extends ExpressionTree> args = ((MethodInvocationTree) valueTree).getArguments();
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.

@PRITI1999 PRITI1999 requested a review from kelloggm July 31, 2020 04:33
Copy link
Contributor

@kelloggm kelloggm left a 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 =
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?

@kelloggm kelloggm merged commit c9c65d1 into typetools:master Aug 3, 2020
@PRITI1999 PRITI1999 deleted the fix-issue-3224 branch August 18, 2020 02:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Arrays.copyOf should propagate length information from argument to return value
2 participants