In Java is it possible to change or modify an enum itself and thus to corrupt an enum singleton?

后端 未结 2 974
抹茶落季
抹茶落季 2021-01-19 12:33

Is it possible to change an enum itself at run-time somehow? E.g. using reflection. The question is not about to change the state of an enum constant. It\'s about to change

2条回答
  •  一整个雨季
    2021-01-19 13:04

    Yes, you can even add new values to enum without any issues, as I already explained here: https://stackoverflow.com/a/51244909/4378853

    So you have multiple ways to break enum:
    1. Use reflections to change value of fields like Color.RED to some other enum value.
    2. Use reflections to change .values by modifying ENUM$VALUES field.
    3. Use reflections to change what MyEnum.class.getEnumConstants() returns by editing T[] enumConstants in Class class.
    4. Similar for Map enumConstantDirectory in Class class - used for Enum.valueOf(MyEnum.class, name) and MyEnum.valueOf(name) .
    5. Use reflections to add new enum constant as described in my linked answer above.

    So for

    Regarding the following enum is it possible to add a color WHITE or remove color RED or to change their order?

    Yes, it is possible.

    Class enumClass = Color.class;
    // first we need to find our constructor, and make it accessible
    Constructor constructor = enumClass.getDeclaredConstructors()[0];
    constructor.setAccessible(true);
    
    // this is this same code as in constructor.newInstance, but we just skipped all that useless enum checks ;)
    Field constructorAccessorField = Constructor.class.getDeclaredField("constructorAccessor");
    constructorAccessorField.setAccessible(true);
    // sun.reflect.ConstructorAccessor -> iternal class, we should not use it, if you need use it, it would be better to actually not import it, but use it only via reflections. (as package may change, and will in java 9)
    ConstructorAccessor ca = (ConstructorAccessor) constructorAccessorField.get(constructor);
    if (ca == null) {
        Method acquireConstructorAccessorMethod = Constructor.class.getDeclaredMethod("acquireConstructorAccessor");
        acquireConstructorAccessorMethod.setAccessible(true);
        ca = (ConstructorAccessor) acquireConstructorAccessorMethod.invoke(constructor);
    }
    // note that real constructor contains 2 additional parameters, name and ordinal
    Color enumValue = (Color) ca.newInstance(new Object[]{"WHITE", 3});// you can call that using reflections too, reflecting reflections are best part of java ;)
    
    
    static void makeAccessible(Field field) throws Exception {
        field.setAccessible(true);
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(field, field.getModifiers() & ~ Modifier.FINAL);
    }
    

    And then just change that field to new value that include our new field:

    Field valuesField = Color.class.getDeclaredField("$VALUES");
    makeAccessible(valuesField);
    // just copy old values to new array and add our new field.
    Color[] oldValues = (Color[]) valuesField.get(null);
    Color[] newValues = new Color[oldValues.length + 1];
    System.arraycopy(oldValues, 0, newValues, 0, oldValues.length);
    newValues[oldValues.length] = enumValue;
    valuesField.set(null, newValues);
    

    And now also use reflections to update that two fields inside Class class.

    Field enumConstantDirectoryField = Class.class.getDeclaredField("enumConstantDirectory");
    enumConstantDirectoryField.setAccessible(true);
    enumConstantDirectoryField.set(Color.class, null);
    Field enumConstantsField = Class.class.getDeclaredField("enumConstants");
    enumConstantsField.setAccessible(true);
    enumConstantsField.set(Color.class, null);
    

    To change order just use this same code as for $VALUES field edit, but just set it to whatever you want.

    You can't only add/remove fields like make Color.WHITE work or make Color.RED disappear (but you can set it to null)

    Also using unsafe it should be possible to declare new class to create new instance of abstract enum classes.
    I used javassist library to reduce code needed to generate new class:

    public class Test {
        public static void main(String[] args) throws Exception {
            System.out.println(MyEnum.VALUE.getSomething()); // prints 5
    
            ClassPool classPool = ClassPool.getDefault();
            CtClass enumCtClass = classPool.getCtClass(MyEnum.class.getName());
            CtClass ctClass = classPool.makeClass("com.example.demo.MyEnum$2", enumCtClass);
    
            CtMethod getSomethingCtMethod = new CtMethod(CtClass.intType, "getSomething", new CtClass[0], ctClass);
            getSomethingCtMethod.setBody("{return 3;}");
            ctClass.addMethod(getSomethingCtMethod);
    
            Constructor unsafeConstructor = Unsafe.class.getDeclaredConstructors()[0];
            unsafeConstructor.setAccessible(true);
            Unsafe unsafe = (Unsafe) unsafeConstructor.newInstance();
    
            MyEnum newInstance = (MyEnum) unsafe.allocateInstance(ctClass.toClass());
            Field singletonInstance = MyEnum.class.getDeclaredField("VALUE");
            makeAccessible(singletonInstance);
            singletonInstance.set(null, newInstance);
    
            System.out.println(MyEnum.VALUE.getSomething()); // prints 3
        }
    
        static void makeAccessible(Field field) throws Exception {
            field.setAccessible(true);
            Field modifiersField = Field.class.getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(field, field.getModifiers() & ~ Modifier.FINAL);
        }
    }
    
    enum MyEnum {
        VALUE {
            @Override
            public int getSomething() {
                return 5;
            }
        };
    
        public abstract int getSomething();
    }
    

    This will print 5 and then 3. Note that this is impossible to enum classes that does not contain subclasses - so without any overriden methods, as then enum is declared as final class.

    And you can use that to break singleton pattern without any issues, just set it to null, or you can even try to use unsafe to implement own version of it at runtime and replace instance of it, or just create multiple instances of it.
    This is why I prefer just simple class with private constructor and holder class - if someone want to break it, they will do it anyway. But at least you don't use enum to something it is not designed for, so code is easier to read.

提交回复
热议问题