Ivan Ponomarev created LANG-1815:
------------------------------------

             Summary: AnnotationUtils.equals(..) returns false for equal 
package-private annotations due to reflective access failure
                 Key: LANG-1815
                 URL: https://issues.apache.org/jira/browse/LANG-1815
             Project: Commons Lang
          Issue Type: Bug
          Components: lang.*
            Reporter: Ivan Ponomarev


h3. Description

{{AnnotationUtils.equals(Annotation a1, Annotation a2)}} is intended to 
implement the equality rules described by {{Annotation.equals(Object)}} (as 
stated in its Javadoc). However, the current implementation can incorrectly 
return false for equal annotations when the annotation type is not reflectively 
accessible from the caller’s package.

The issue is caused by handling reflective failures via:

{code:java}
} catch (final ReflectiveOperationException ex) {
    return false;
}
{code}

This is problematic because reflective access errors are not a “not equal” 
condition; they indicate that the implementation failed to read an annotation 
member value. Returning {{false}} silently violates the expected semantics and 
makes the method’s result depend on the caller’s package/module access rather 
than annotation content.

A concrete case is a package-private annotation type accessed from outside 
{{org.apache.commons.lang3}}: invoking its member method reflectively can throw 
an access-related reflective exception, which is currently swallowed and 
converted to {{false}}.

(!) To reproduce, the test class must be *outside* the 
{{org.apache.commons.lang3}} package.
{code:java}
package org.apache.commons.lang3.external;

import org.apache.commons.lang3.AnnotationUtils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

// CAUTION: in order to reproduce the ReflectiveOperationException bug, this 
test
// MUST be located OUTSIDE org.apache.commons.lang3 package.
// Do NOT move it to the org.apache.commons.lang3 package!
public class AnnotationEqualsTest {

    @Retention(RetentionPolicy.RUNTIME)
    @interface Tag {
        String value();
    }

    @Tag("value")
    private final Object a = new Object();
    @Tag("value")
    private final Object b = new Object();

    @Test
    void equalsWorksOnPackagePrivateAnnotations() throws Exception {
        Tag tagA = getClass().getDeclaredField("a").getAnnotation(Tag.class);
        Tag tagB = getClass().getDeclaredField("b").getAnnotation(Tag.class);

        // Expected true; currently returns false because a reflective access 
exception is swallowed.
        Assertions.assertTrue(AnnotationUtils.equals(tagA, tagB));
    }
}

{code}

h3.Proposed fix


# Ensure member methods are accessible before invocation via 
{{m.setAccessible(true);}} This change makes the reproducer test "green".
# Do not silently convert reflective failures into {{false}}, wrap and rethrow 
into {{IllegalStateException}} instead. This makes failures visible and 
prevents incorrect “false” results caused by reflective access issues.





--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to