我开始
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在索引61处,我们看到将三个枚举常量分配给名称为的静态数组
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$ColorGREEN: class playground.ReflectEnum$ColorBLUE: class playground.ReflectEnum$ColorENUM$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:1RED:0BLUE:2
显然,顺序已更改。
由于可以为数组分配值,因此分配也是有效的
null。
Color[] colors = getEnumsArray(Color.class);colors[0] = Color.GREEN;colors[1] = Color.RED;colors[2] = null;
列出枚举常量显示:
GREEN:1RED:0Exception 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")。此枚举常量未设置为null。因此,它完全打破了
valueOf方法
Color。
但是
Enum.valueOf(Color.class, "BLUE")仍然解析枚举常量
Color.BLUE。
由于枚举数组被声明为
staticfinal我遵循的,因此使用Java反射来更改private static
final字段来创建和设置新的枚举数组。
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)
编辑1(在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]
枚举数组已被另一个替换。
编辑2(在GotoFinal的注释之根据GotoFinal的答案,关于如何在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; } }重用代码GotoFinal已显示我定义了以下方法:
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 = theNewoneSingleton 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 onlyThe new one![The new one!]
总结:
可以在运行时修改一个枚举,并用另一个替换该枚举数组。但是至少设置一个枚举常量会
null
破坏VM中定义的枚举的一致性。尽管可以根据GotoFinal的答案解决此问题。使用实现单例时
enum
,可以用另一个枚举实例替换唯一的实例-至少对于知道其实现的某些JDK / JRE而言。如果枚举构造函数具有参数,则新创建的枚举实例可以利用它们来植入恶意行为。



