Theoretical race condition in ECJ enum switches

Jonas Konrad via Concurrency-interest <[email protected]> Fri, 19 Mar 2021 13:55:14 +0100
Newsgroups gmane.comp.java.jsr.166-concurrency
Message-ID <[email protected]>
Hi everyone,

I found a potential race condition in how ECJ generates enum switch 
tables. It's not serious, and I'm wondering if it can be triggered at 
all, which is why I'm asking here, and not on the eclipse bug tracker :)

In particular, in this code:

public class Main {
     private static final E e = E.A;

     void test() {
         switch (e) {
             default:
                 throw new AssertionError();
             case A:
         }
     }

     enum E {
         A
     }
}

The Main class desugars to:

public class Main {
     private static final E e = E.A;
     private static /* synthetic */ int[] $SWITCH_TABLE$Main$E;

     void test() {
         switch ($SWITCH_TABLE$Main$E()[Main.e.ordinal()]) {
             default: {
                 throw new AssertionError();
             }
             case 1: {}
         }
     }

     static /* synthetic */ int[] $SWITCH_TABLE$Main$E() {
         final int[] $switch_TABLE$Main$E = Main.$SWITCH_TABLE$Main$E;
         if ($switch_TABLE$Main$E != null) {
             return $switch_TABLE$Main$E;
         }
         final int[] $switch_TABLE$Main$E2 = new int[E.values().length];
         try {
             $switch_TABLE$Main$E2[E.A.ordinal()] = 1;
         }
         catch (NoSuchFieldError noSuchFieldError) {}
         return Main.$SWITCH_TABLE$Main$E = $switch_TABLE$Main$E2;
     }
}

(full code at https://javap.yawk.at/#YCwS2S/procyon )

As you can see, ECJ uses a lazy initialization pattern without any 
synchronization. If two threads call test() at roughly the same time, 
the first thread might write the Main.$SWITCH_TABLE$Main$E field, and 
the second thread might read it back as the proper array, returning it 
normally. However, because there is no HB edge between the write and the 
read, the second thread may not observe the values in the array. It 
could still see the default 0 values, which would then trigger the 
AssertionError in test().

Now, it's pretty clear that this is technically a race condition, but I 
wonder if it is possible to trigger it at all in the real world. The 
race condition can only happen on the first few invocations of the 
method after the class has been loaded. Additionally, the test() method 
will not be hot at that point, so JIT artifacts are out (maybe -Xcomp or 
graal could work, though). Another idea would be to exploit the CPU 
semantics.

Can anyone trigger this bug?

Cheers,
- Jonas
_______________________________________________
Concurrency-interest mailing list
[email protected]
http://cs.oswego.edu/mailman/listinfo/concurrency-interest