Skip to content

[Java] Question on Overrides of Generic Methods #3490

Closed

Description

@intrigus-lgtm

Description of the issue
https://help.semmle.com/QL/learn-ql/java/types-class-hierarchy.html says:

/** class representing java.util.Collection.toArray(T[]) */
class CollectionToArray extends Method {
   CollectionToArray() {
       this.getDeclaringType().hasQualifiedName("java.util", "Collection") and
       this.hasName("toArray") and
       this.getNumberOfParameters() = 1
   }
}

/** class representing calls to java.util.Collection.toArray(T[]) */
class CollectionToArrayCall extends MethodAccess {
   CollectionToArrayCall() {
       exists(CollectionToArray m |
           this.getMethod().getSourceDeclaration().overrides*(m)
       )
   }

   /** the call's actual return type, as determined from its argument */
   Array getActualReturnType() {
       result = this.getArgument(0).getType()
   }
}

Notice the use of getSourceDeclaration and overrides in the constructor of CollectionToArrayCall: we want to find calls to Collection.toArray and to any method that overrides it, as well as any parameterized instances of these methods. In our example above, for instance, the call l.toArray resolves to method toArray in the raw class ArrayList.

Running this simple query has zero results.
If the query is modified to not require that the method is declared in a specific type,
it has exactly one result in UniquePropertyMessageEvictionStrategy.java

Question:
Why does the query, that requires the method to be declared in a specific type, not work?
If I understand the documentation correctly, this should work.
Am I misunderstanding what " and to any method that overrides it" means?

Activity

  1. aschackmull commented on May 22, 2020

    @aschackmull
    Contributor

    Thank you for finding this bug in the tutorial. We'll get that fixed. The TLDR of this is that you can use either .overridesOrInstantiates*(m) or .getASourceOverriddenMethod*() = m in place of .overrides*(m) for this to work properly.
    The longer explanation of what goes wrong is this:
    Consider a class MyListCollection<E> implements Collection<List<E>>. In this class the method MyListCollection<E>::toArray overrides Collection<List<E>>::toArray, which is is an instantiation of Collection<T>::toArray (modelled here as the QL class CollectionToArray), so if we only look for straight overrides we won't reach CollectionToArray. This example highlights the issue, but the same thing happens for all other subtypes of Collection. Take for instance List<E> extends Collection<E>; again List<E>::toArray overrides Collection<E>::toArray, which isn't the same method as Collection<T>::toArray since one type variable is the one from the definition of List and the other is the one from the definition of Collection. I've used the names E and T here to highlight the difference, but they could equally well both be named T without changing the issue. With all this in mind we could also use yet another (slightly more verbose fix):

       CollectionToArrayCall() {
           exists(CollectionToArray m, Method overridden |
               this.getMethod().getSourceDeclaration().overrides*(overridden) and
               overridden.getSourceDeclaration() = m
           )
       }
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionFurther information is requested

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions