Bug 544521 - Enum switch lookup table is not safely published, according to Java Memory Model
Summary: Enum switch lookup table is not safely published, according to Java Memory Model
Status: VERIFIED FIXED
Alias: None
Product: JDT
Classification: Eclipse Project
Component: Core (show other bugs)
Version: 4.11   Edit
Hardware: All All
: P3 normal (vote)
Target Milestone: 4.17 RC1   Edit
Assignee: Andrey Loskutov CLA
QA Contact: Jay Arthanareeswaran CLA
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2019-02-17 22:46 EST by Tagir Valeev CLA
Modified: 2020-08-28 03:58 EDT (History)
5 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Tagir Valeev CLA 2019-02-17 22:46:50 EST
Currently Eclipse compiler translates enum switch by creating a private static non-final int[] field named like $SWITCH_TABLE$EnumName and lazily fills it in the static method with the same name. The generated bytecode is roughly equivalent to the following Java program:

private static int[] $SWITCH_TABLE$EnumName;

static int[] $SWITCH_TABLE$EnumName() {
    int[] tmp = $SWITCH_TABLE$EnumName;
    if (tmp != null) {
        return tmp;
    }
    tmp = new int[EnumName.values().length];
    try {
        tmp[EnumName.Val1.ordinal()] = 1;
    } catch (NoSuchFieldError ignored) {
    }
    try {
        tmp[EnumName.Val2.ordinal()] = 2;
    } catch (NoSuchFieldError ignored) {
    }
    ...
    return $SWITCH_TABLE$EnumName = tmp;
}

This method performs unsafe publication of $SWITCH_TABLE$EnumName, according to Java Memory Model. As values of int[] array are not final (array elements cannot be final) and $SWITCH_TABLE$EnumName is not volatile, no guarantees about ordering are made by JMM. In particular it's possible that `$SWITCH_TABLE$EnumName = tmp` write becomes visible in another thread prior to `tmp[EnumName.Val1.ordinal()] = 1` write. Thus, it's possible in valid JVM implementation that in another thread `tmp != null` check would return true and the corresponding switch statement would see yet-uninitialized elements of array erroneously going into the default switch branch.

I cannot demonstrate it on the hardware and JVM implementation I have, because it's only single chance to step on it due to the class lifetime, and I'm not an expert in reproducing such kind of bugs. Probably it could be demonstrated on some JVM implementation using some existing hardware and some valid combination of VM options. But even if not, it doesn't mean that there's no problem. Spec is violated and future hardware/JIT improvements/memory layout changes could make it happen.

The problem could be easily fixed declaring the synthetic field $SWITCH_TABLE$EnumName as `volatile`. This would introduce a happens-before edge between field write and subsequent reads and ensure that all previous writes (including writes to the array) are visible after the field read.

More evident concurrency problem was fixed in bug#99282, but the fix still violates JMM.
Comment 1 Jay Arthanareeswaran CLA 2019-02-18 01:42:54 EST
Tagir,

Thanks for the bug report and the proposed fix. I am not an expert in this area but your proposal sounds plausible at a quick look.

We will take a look during 4.12.
Comment 2 Manoj N Palat CLA 2019-08-27 01:08:45 EDT
Bulk move to 4.14
Comment 3 Manoj N Palat CLA 2019-08-27 02:06:45 EDT
Bulk move out of 4.13
Comment 4 Some User CLA 2019-10-24 11:12:07 EDT
StackOverflow question: "Is Eclipse's $SWITCH_TABLE$ thread-safe?" (https://stackoverflow.com/q/58507978)

https://github.com/Marcono1234/eclipse-enum-jcstress uses jcstress to test code which is similar to the one ecj would generate. However, the field is not static since, as mentioned by Tagir, it would make testing a lot more complicated because the field might only be initialized once per JVM.
The test fails indeed (might have to run it multiple times) which means the current code is not thread-safe (unless the test code / setup is faulty).
Comment 5 Eclipse Genie CLA 2019-12-06 10:49:55 EST
New Gerrit change created: https://git.eclipse.org/r/154023
Comment 6 Andrey Loskutov CLA 2019-12-06 10:54:11 EST
(In reply to Eclipse Genie from comment #5)
> New Gerrit change created: https://git.eclipse.org/r/154023

This is basically the proposal from Tagir and makes the generated table field volatile. Would be nice if someone who understand this business could review that.
Comment 7 Some User CLA 2019-12-07 08:47:55 EST
Andrey, to me this looks good. Though I might not have enough experience with the topic.

However, what I noticed is that the code it generates for enum switches inside interfaces is rather weird. If I see that correctly it makes the switch array final and then never initializes it, so it always creates the temporary switch array. Should a separate issue be created for this, or do you want to solve it here as well?

Can be seen for example with:

public interface InterfaceEnumSwitch {
    enum EnumTest {
        FIRST,
        SECOND
    }
    
    static int get(EnumTest e) {
        switch (e) {
            case FIRST:
                return 1;
            case SECOND:
                return 2;
        }
        
        return -1;
    }
}
Comment 8 Jay Arthanareeswaran CLA 2020-05-18 05:28:22 EDT
Looks like fallen between cracks. Will revisit during 4.17.

Andrey, meanwhile, please see if you can respond to comment #7
Comment 9 Andrey Loskutov CLA 2020-05-18 05:41:37 EDT
(In reply to Some User from comment #7)
> Andrey, to me this looks good. Though I might not have enough experience
> with the topic.
> 
> However, what I noticed is that the code it generates for enum switches
> inside interfaces is rather weird. If I see that correctly it makes the
> switch array final and then never initializes it, so it always creates the
> temporary switch array. Should a separate issue be created for this, or do
> you want to solve it here as well?

Would be great if we would have a dedicated bug for this.
Comment 10 Jay Arthanareeswaran CLA 2020-08-24 01:54:18 EDT
Andrey,

Please go ahead with the fix for RC1. Comment #7 can be taken up with a different bug.
Comment 12 Andrey Loskutov CLA 2020-08-24 04:55:38 EDT
I've merged the "volatile" fix and created bug 566313 for comment 7.
Comment 13 Jay Arthanareeswaran CLA 2020-08-25 01:21:52 EDT
Verified for 4.17 RC1 using build I20200824-1900.