Skip to content
New issue

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

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

Already on GitHub? # to your account

com.strobel.reflection.TypeCache can store incorrect association between Object[] and array of generic parameters #41

Closed
BladeWise opened this issue Feb 5, 2022 · 0 comments
Assignees

Comments

@BladeWise
Copy link

The TypeCache class generates the same descriptor for both Object[] and T[], causing incorrect association of a descriptor to a type, depending on which type populates the cache first.

Below code provides a test example

package com.strobel.reflection;

import org.junit.Test;

import java.util.Arrays;

import static org.junit.Assert.assertEquals;

public class CustomTests {
    @Test
    public void genericParameterArrayTypesDoNotCollideInTypeCache() {
        final Type<MyClass> c1 = Type.of(MyClass.class); // Load class with method having T[] parameter
        c1.getMethods();  // Load methods of class with method having T[] parameter
        final Type<MyOtherClass> c2 = Type.of(MyOtherClass.class); // Load class with method having Object[] parameter
        final Type<Object[]> objectArrayType = Types.Object.makeArrayType(); // Prepare Object[] type for comparison
        final MethodInfo method = c2.getMethod("getFirstOrNull", objectArrayType); // Load specific method having Object[] parameter
        final Type<?> methodParameterType = method.getParameters().getParameterTypes().get(0); // Retrieve method parameter type (expected to be Object[])
        assertEquals(objectArrayType, methodParameterType); // The method parameter type should match the expected Object[], but will be T[]
    }

    private static class MyClass {
        public static <T> Iterable<T> enumerate(final T[] items) {
            return Arrays.asList(items);
        }
    }

    private static class MyOtherClass {
        public static Object getFirstOrNull(final Object... values) {
            return values.length > 0 ? values[0] : null;
        }
    }
}

Above code fails, unsless c2 method is loaded before c1 methods.

The cause is that

this.descriptor = type.getInternalName();

calls the getInternalName of an ArrayType, which returns
return "[" + _elementType.getErasedSignature();

which is [Ljava/lang/Object; for both Object[] and T[].

If the T[] is loaded first, it will then populate the cache _definitionMap, as per


storing T[] as the equivalent type of [Ljava/lang/Object;. Once updated, it will not change anymore.

Due to this, the getFirstOrNull method above will incorrectly return that the array type used as parameter is T[], instead of Object[]

If I did not misunderstood the purpose of the TypeCache._map, it wants to store the most recent instance of a type, matching the descriptor and generic type arguments.
If so, in my opinion the descriptor could be changed like this:

            this.descriptor =
                type.isArray() && type.getElementType().isGenericParameter() ? "[L" + type.getElementType().getInternalName() + ";" : type.getInternalName();

But I am not really sure this is the expected behavior... could you explain a bit the _map purpose, expecially regarding generic types (both bounded and unbounded)?

@mstrobel mstrobel self-assigned this Feb 8, 2022
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants