FarmersWrap opened a new pull request, #54:
URL: https://github.com/apache/openwebbeans/pull/54

   # Background
   I found the flaky behavior in 
org.apache.webbeans.test.specalization.observer.prot.ProtectedObserverTest#testObserverMethodsInParentOfSpecializedBeans
 with an open-source research tool 
[NonDex](https://github.com/TestingResearchIllinois/NonDex), which will shuffle 
implementation-dependent operations.  
   The test is as below. 
   ```
       @Test
       public void testEquals2AnnotationsUnorderedParam()
       {
           BeanCacheKey a = new BeanCacheKey(true, String.class, null, it -> 
null, a12);
           BeanCacheKey b = new BeanCacheKey(true, String.class, null, it -> 
null, a21);
           Assert.assertEquals(a, b);
           Assert.assertEquals(a.hashCode(), b.hashCode());
       }
   ```
   In BeanCacheKey(), it sorts the annotation array using AnnotationComparator
   ```
   public final class BeanCacheKey
   {
       private static final Comparator<Annotation> ANNOTATION_COMPARATOR = new 
AnnotationComparator();
   
       private final boolean isDelegate;
       private final Type type;
       private final String path;
       private final Annotation qualifier;
       private final Annotation[] qualifiers;
       private final int hashCode;
       private volatile LazyAnnotatedTypes lazyAnnotatedTypes; // only needed 
for the "main" key
       private final Function<Class<?>, AnnotatedType<?>> lazyAtLoader;
   
       public BeanCacheKey(boolean isDelegate, Type type, String path,
                           Function<Class<?>, AnnotatedType<?>> lazyAtLoader,
                           Annotation... qualifiers)
       {
           this.isDelegate = isDelegate;
           this.type = type;
           this.path = path;
           this.lazyAtLoader = lazyAtLoader;
           int length = qualifiers != null ? qualifiers.length : 0;
           if (length == 0)
           {
               qualifier = null;
               this.qualifiers = null;
           }
           else if (length == 1)
           {
               qualifier = qualifiers[0];
               this.qualifiers = null;
           }
           else
           {
               qualifier = null;
               // to save array creations, we only create an array, if we have 
more than one annotation
               this.qualifiers = new Annotation[length];
               System.arraycopy(qualifiers, 0, this.qualifiers, 0, length);
               Arrays.sort(this.qualifiers, ANNOTATION_COMPARATOR);
           }
   
           // this class is directly used in ConcurrentHashMap.get() so simply 
init the hasCode here
           hashCode = computeHashCode();
       }
   ```
   AnnotationComparator uses getDeclaredMethods() to get a Method[], and the 
comparator will compare the Method[] using its elements. We want member1 and 
member2 to compare the same type of element during the comparison. We do not 
want to use a "name" to compare with a "number".
   So, we sorted the array to get it fixed. The Method[]. will only be used in 
this comparator.
   ```
       private static class AnnotationComparator implements 
Comparator<Annotation>
       {
   
           // Notice: Sorting is a bit costly, but the use of this code is very 
rar.
           @Override
           public int compare(Annotation annotation1, Annotation annotation2)
           {
               Class<? extends Annotation> type1 = annotation1.annotationType();
               Class<? extends Annotation> type2 = annotation2.annotationType();
               int temp = type1.getName().compareTo(type2.getName());
               if (temp != 0)
               {
                   return temp;
               }
               if (annotation1 instanceof EmptyAnnotationLiteral || annotation2 
instanceof EmptyAnnotationLiteral)
               {
                   // if any of those 2 annotations are known to have no members
                   // then we can immediately return as we know the 2 
annotations mean the same
                   return 0;
               }
   
               Method[] member1 = type1.getDeclaredMethods();
               Arrays.sort(member1, (m1, m2) -> 
m1.getName().compareTo(m2.getName()));
               Method[] member2 = type2.getDeclaredMethods();
               Arrays.sort(member2, (m1, m2) -> 
m1.getName().compareTo(m2.getName()));
   
               // TBD: the order of the list of members seems to be 
deterministic
   
               int i = 0;
               int j = 0;
               int length1 = member1.length;
               int length2 = member2.length;
   
               // find next nonbinding
               for (;; i++, j++)
               {
                   while (i < length1 && 
member1[i].isAnnotationPresent(Nonbinding.class))
                   {
                       i++;
                   }
                   while (j < length2 && 
member2[j].isAnnotationPresent(Nonbinding.class))
                   {
                       j++;
                   }
                   if (i >= length1 && j >= length2)
                   { // both ended
                       return 0;
                   }
                   else if (i >= length1)
                   { // #1 ended
                       return 1;
                   }
                   else if (j >= length2)
                   { // #2 ended
                       return -1;
                   }
                   else
                   { // not ended
                       int c = 
member1[i].getName().compareTo(member2[j].getName());
                       if (c != 0)
                       {
                           return c;
                       }
                       Object value1 = callMethod(annotation1, member1[i]);
                       Object value2 = callMethod(annotation2, member2[j]);
                       assert value1.getClass().equals(value2.getClass());
   
                       if (value1 instanceof Comparable)
                       {
                           c = ((Comparable)value1).compareTo(value2);
                           if (c != 0)
                           {
                               return c;
                           }
                       }
                       else if (value1.getClass().isArray())
                       {
                           c = value1.getClass().getComponentType().getName()
                                   
.compareTo(value2.getClass().getComponentType().getName());
                           if (c != 0)
                           {
                               return c;
                           }
   
                           int length = Array.getLength(value1);
                           c = length - Array.getLength(value2);
                           if (c != 0)
                           {
                               return c;
                           }
                           for (int k = 0; k < length; k++)
                           {
                               c = ((Comparable)Array.get(value1, 
k)).compareTo(Array.get(value2, k));
                               if (c != 0)
                               {
                                   return c;
                               }
                           }
   
                       }
                       else if (value1 instanceof Class)
                       {
   
                           c = ((Class)value1).getName().compareTo(((Class) 
value2).getName());
                           if (c != 0)
                           {
                               return c;
                           }
                       }
                       else
                       {
                           // valid types for members are only Comparable, 
Arrays, or Class
                           assert false;
                       }
                   }
               }
           }
       }
   ```
   The modification can be found in the commit.
   The normal run has passed but it fails with nondex.
   After adding sorting algorithm, no failure was reported after 500 runs
   # Does this PR introduce any user-facing change?
   No
   
   # Is the change a dependency upgrade?
   No
   
   # How was this patch tested?
   Test Environment:
   ```
   openjdk version "11.0.20.1"
   Apache Maven 3.6.3
   Ubuntu 20.04.6 LTS
   Linux version: 5.4.0-163-generic
   ```


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: dev-unsubscr...@openwebbeans.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org

Reply via email to