代码之家  ›  专栏  ›  技术社区  ›  LuCio

在Java中,是否可以更改或修改枚举本身,从而破坏枚举单?

  •  4
  • LuCio  · 技术社区  · 6 年前

    是否可以在运行时以某种方式更改枚举本身?例如,使用反射。这个问题不会改变枚举常量的状态。它将更改枚举的常量集或删除任何常量。

    关于以下枚举是否可以添加颜色 WHITE 或删除颜色 RED 或者改变他们的顺序?

    public enum Color {
    
      RED, GREEN, BLUE;
    
    }
    

    我为什么问?

    我知道这个问题有点恶意。但即使约书亚·布洛克也提到 巧妙的攻击 “当讨论(1)实现单例时,建议使用枚举单例模式。如果我们可以修改一个枚举,那么对这个模式的攻击是否可行?

    我试着解决这个问题,并部分地解决了它。我会把我的结果作为答案张贴在下面 advice .


    (1)见 What is an efficient way to implement a singleton pattern in Java? 下面的链接指向 有效的java_重新加载.pdf ,第31页。

    2 回复  |  直到 6 年前
        1
  •  5
  •   LuCio    6 年前

    Color javap -c

     static {};
        Code:
           0: new           #1                  // class playground/Color
           3: dup           
           4: ldc           #14                 // String RED
           6: iconst_0      
           7: invokespecial #15                 // Method "<init>":(Ljava/lang/String;I)V
          10: putstatic     #19                 // Field RED:Lplayground/Color;
          13: new           #1                  // class playground/Color
          16: dup           
          17: ldc           #21                 // String GREEN
          19: iconst_1      
          20: invokespecial #15                 // Method "<init>":(Ljava/lang/String;I)V
          23: putstatic     #22                 // Field GREEN:Lplayground/Color;
          26: new           #1                  // class playground/Color
          29: dup           
          30: ldc           #24                 // String BLUE
          32: iconst_2      
          33: invokespecial #15                 // Method "<init>":(Ljava/lang/String;I)V
          36: putstatic     #25                 // Field BLUE:Lplayground/Color;
          39: iconst_3      
          40: anewarray     #1                  // class playground/Color
          43: dup           
          44: iconst_0      
          45: getstatic     #19                 // Field RED:Lplayground/Color;
          48: aastore       
          49: dup           
          50: iconst_1      
          51: getstatic     #22                 // Field GREEN:Lplayground/Color;
          54: aastore       
          55: dup           
          56: iconst_2      
          57: getstatic     #25                 // Field BLUE:Lplayground/Color;
          60: aastore       
          61: putstatic     #27                 // Field ENUM$VALUES:[Lplayground/Color;
          64: return        
    

    ENUM$VALUES

    Field[] declaredFields = Color.class.getDeclaredFields();
    for (Field field : declaredFields) {
      if (Modifier.isStatic(field.getModifiers())) {
        System.out.println(field.getName() + ": " + field.getType());
      }
    }
    

    RED: class playground.ReflectEnum$Color
    GREEN: class playground.ReflectEnum$Color
    BLUE: class playground.ReflectEnum$Color
    ENUM$VALUES: class [Lplayground.ReflectEnum$Color;
    

      protected static <E extends Enum<E>> E[] getEnumsArray(Class<E> ec) throws Exception {
        Field field = ec.getDeclaredField("ENUM$VALUES");
        field.setAccessible(true);
        return (E[]) field.get(ec);
      }
    

    Color[] colors = getEnumsArray(Color.class);
    colors[0] = Color.GREEN;
    colors[1] = Color.RED;
    colors[2] = Color.BLUE;
    

    for (Color color : Color.values()) {
      System.out.println(action + ":" + color.ordinal());
    }
    

    GREEN:1
    RED:0
    BLUE:2
    

    null

    Color[] colors = getEnumsArray(Color.class);
    colors[0] = Color.GREEN;
    colors[1] = Color.RED;
    colors[2] = null;
    

    GREEN:1
    RED:0
    Exception in thread "main" java.lang.NullPointerException
        at playground.ReflectEnum.main(ReflectEnum.java:57)
    

    System.out.println(Color.valueOf("GREEN"));
    System.out.println(Color.valueOf("RED"));
    System.out.println(Color.valueOf("BLUE"));
    

    Exception in thread "main" java.lang.NullPointerException
        at java.lang.Class.enumConstantDirectory(Class.java:3236)
        at java.lang.Enum.valueOf(Enum.java:232)
        at playground.Color.valueOf(Color.java:1)
        at playground.ReflectEnum.main(ReflectEnum.java:48)
    

    ReflectEnum.java:48 Color.valueOf("GREEN") valueOf

    Enum.valueOf(Color.class, "BLUE") Color.BLUE

    static final Change private static final field using Java reflection

      protected static <E extends Enum<E>> void setEnumsArray(Class<E> ec, E... e) throws Exception {
        Field field = ec.getDeclaredField("ENUM$VALUES");
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        field.setAccessible(true);
        modifiersField.setAccessible(true);
        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        field.set(ec, e);
      }
    

    setEnumsArray(Color.class, Color.BLUE, Color.GREEN, Color.RED, Color.BLUE)

    Exception in thread "main" java.lang.IllegalAccessException: Can not set static final [Lplayground.Color; field playground.Color.ENUM$VALUES to [Lplayground.Color;
        at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:76)
        at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:80)
        at sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl.set(UnsafeQualifiedStaticObjectFieldAccessorImpl.java:77)
        at java.lang.reflect.Field.set(Field.java:758)
        at playground.ReflectEnum.setEnumsArray(ReflectEnum.java:76)
        at playground.ReflectEnum.main(ReflectEnum.java:37)
    

    Radiodef

      protected static Field getEnumsArrayField(Class<?> ec) throws Exception {
        Field field = ec.getDeclaredField("ENUM$VALUES");
        field.setAccessible(true);
        return field;
      }
    
      protected static void clearFieldAccessors(Field field) throws ReflectiveOperationException {
        Field fa = Field.class.getDeclaredField("fieldAccessor");
        fa.setAccessible(true);
        fa.set(field, null);
    
        Field ofa = Field.class.getDeclaredField("overrideFieldAccessor");
        ofa.setAccessible(true);
        ofa.set(field, null);
    
        Field rf = Field.class.getDeclaredField("root");
        rf.setAccessible(true);
        Field root = (Field) rf.get(field);
        if (root != null) {
          clearFieldAccessors(root);
        }
    

    System.out.println(Arrays.toString((Object[]) getEnumsArrayField(Color.class).get(null)));
    clearFieldAccessors(getEnumsArrayField(Color.class));
    setEnumsArray(Color.class, Color.BLUE, Color.GREEN, Color.RED, Color.BLUE);
    System.out.println(Arrays.toString(Color.values()));
    

    [RED, GREEN, BLUE]
    [BLUE, GREEN, RED, BLUE]
    


    GotoFinal
    answer How to create an instance of enum using reflection in java?

      public enum Singleton {
    
        INSTANCE("The one and only");
    
        private String description;
    
        private Singleton(String description) {
          this.description = description;
        }
    
        @Override
        public String toString() {
          return description;
        }
    
      }
    

      protected static Singleton createEnumValue(String name, int ordinal, String description) throws Exception {
        Class<Singleton> monsterClass = Singleton.class;
        Constructor<?> constructor = monsterClass.getDeclaredConstructors()[0];
        constructor.setAccessible(true);
    
        Field constructorAccessorField = Constructor.class.getDeclaredField("constructorAccessor");
        constructorAccessorField.setAccessible(true);
        sun.reflect.ConstructorAccessor ca = (sun.reflect.ConstructorAccessor) constructorAccessorField.get(constructor);
        if (ca == null) {
          Method acquireConstructorAccessorMethod = Constructor.class.getDeclaredMethod("acquireConstructorAccessor");
          acquireConstructorAccessorMethod.setAccessible(true);
          ca = (sun.reflect.ConstructorAccessor) acquireConstructorAccessorMethod.invoke(constructor);
        }
        Singleton enumValue = (Singleton) ca.newInstance(new Object[] { name, ordinal, description });
        return enumValue;
      }
    
     protected static <E extends Enum<E>> void setFinalField(Class<E> ec, Field field, E e) throws NoSuchFieldException, IllegalAccessException {
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        field.setAccessible(true);
        modifiersField.setAccessible(true);
        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        field.set(ec, e);
      }
    

    System.out.println(Singleton.INSTANCE.toString());
    // setting INSTANCE = theNewOne
    Singleton theNewOne = createEnumValue(Singleton.INSTANCE.name(), Singleton.INSTANCE.ordinal(), "The new one!");
    setFinalField(Singleton.class, Singleton.class.getDeclaredField(Singleton.INSTANCE.name()), theNewOne);
    System.out.println(Singleton.INSTANCE.toString());
    // setting enum array = [theNewOne]
    clearFieldAccessors(getEnumsArrayField(Singleton.class));
    setEnumsArray(Singleton.class, theNewOne);
    System.out.println(Arrays.toString(Singleton.values()));
    

    The one and only
    The new one!
    [The new one!]
    

    • enum

        2
  •  4
  •   GotoFinal    6 年前

    https://stackoverflow.com/a/51244909/4378853



    ENUM$VALUES
    MyEnum.class.getEnumConstants() T[] enumConstants Class
    Map<String, T> enumConstantDirectory Enum.valueOf(MyEnum.class, name) MyEnum.valueOf(name)

    Class<Color> 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);
    }
    

    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);
    

    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);
    

    $VALUES

    Color.WHITE Color.RED


    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();
    }