|
1 | 1 | package org.embeddedt.modernfix.util; |
2 | 2 |
|
3 | | -import com.google.common.collect.ImmutableMap; |
4 | 3 | import org.embeddedt.modernfix.ModernFix; |
5 | 4 | import org.embeddedt.modernfix.core.ModernFixMixinPlugin; |
6 | | -import org.jetbrains.annotations.NotNull; |
7 | | -import org.jetbrains.annotations.Nullable; |
| 5 | +import org.objectweb.asm.tree.ClassNode; |
| 6 | +import org.spongepowered.asm.mixin.MixinEnvironment; |
| 7 | +import org.spongepowered.asm.mixin.extensibility.IMixinInfo; |
8 | 8 | import org.spongepowered.asm.mixin.transformer.ClassInfo; |
9 | 9 |
|
10 | 10 | import java.lang.reflect.Field; |
11 | | -import java.security.CodeSigner; |
12 | | -import java.util.*; |
13 | | -import java.util.jar.Attributes; |
| 11 | +import java.util.Collection; |
| 12 | +import java.util.Map; |
14 | 13 |
|
15 | 14 | public class ClassInfoManager { |
16 | | - private static Map<String, ClassInfo> classInfoCache = null; |
| 15 | + private static boolean hasRun = false; |
17 | 16 | public static void clear() { |
18 | | - if(!ModernFixMixinPlugin.instance.isOptionEnabled("perf.clear_mixin_classinfo.ClassInfoManager")) |
| 17 | + if (!ModernFixMixinPlugin.instance.isOptionEnabled("perf.clear_mixin_classinfo.ClassInfoManager") || hasRun) |
19 | 18 | return; |
20 | | - if(classInfoCache == null) { |
21 | | - try { |
22 | | - Field field = ClassInfo.class.getDeclaredField("cache"); |
23 | | - field.setAccessible(true); |
24 | | - classInfoCache = (Map<String, ClassInfo>)field.get(null); |
25 | | - } catch(ReflectiveOperationException | RuntimeException e) { |
26 | | - e.printStackTrace(); |
27 | | - return; |
28 | | - } |
29 | | - } |
30 | | - try { |
31 | | - classInfoCache.entrySet().removeIf(entry -> !entry.getKey().equals("java/lang/Object") && (entry.getValue() == null || !entry.getValue().isMixin())); |
32 | | - } catch(RuntimeException e) { |
33 | | - e.printStackTrace(); |
34 | | - } |
35 | | - |
36 | | - // Clear manifest entries |
37 | | - int numManifestsCleared = 0; |
38 | | - // TODO port |
39 | | - /* |
40 | | - for(IModFileInfo mod : ModList.get().getModFiles()) { |
41 | | - Manifest manifest = mod.getFile().getSecureJar().getManifest(); |
42 | | - if(manifest.getEntries() instanceof HashMap<String, Attributes> entryMap) { |
43 | | - for (Map.Entry<String, Attributes> entry : entryMap.entrySet()) { |
44 | | - Attributes attributes = entry.getValue(); |
45 | | - if (attributes.size() == 1 && attributes.getValue("SHA-256-Digest") != null) { |
46 | | - try { |
47 | | - entry.setValue(EmptyAttributes.INSTANCE); |
48 | | - numManifestsCleared++; |
49 | | - } catch (RuntimeException ignored) { |
50 | | - } |
51 | | - } |
52 | | - } |
53 | | - } |
54 | | - } |
55 | | -
|
56 | | - */ |
57 | | - if(numManifestsCleared > 0) |
58 | | - ModernFix.LOGGER.info("Cleared {} manifest attributes", numManifestsCleared); |
59 | | - |
60 | | - try { |
61 | | - clearSecureJarStructs(); |
62 | | - } catch(Throwable e) { |
63 | | - ModernFix.LOGGER.error("Couldn't clear Jar structs", e); |
64 | | - } |
65 | | - |
| 19 | + hasRun = true; |
| 20 | + ModernFix.resourceReloadExecutor().execute(ClassInfoManager::doClear); |
66 | 21 | } |
67 | 22 |
|
68 | | - private static void clearSecureJarStructs() throws Throwable { |
69 | | - /* |
70 | | - // Clear Jar signing data |
71 | | - Unsafe unsafe; |
72 | | - Field f = Unsafe.class.getDeclaredField("theUnsafe"); |
| 23 | + private static Field accessible(Field f) { |
73 | 24 | f.setAccessible(true); |
74 | | - unsafe = (Unsafe)f.get(null); |
75 | | -
|
76 | | - Field statusDataField, pendingSignersField, verifiedSignersField; |
77 | | - statusDataField = Jar.class.getDeclaredField("statusData"); |
78 | | - pendingSignersField = Jar.class.getDeclaredField("pendingSigners"); |
79 | | - verifiedSignersField = Jar.class.getDeclaredField("verifiedSigners"); |
80 | | -
|
81 | | - long statusDataOffset = unsafe.objectFieldOffset(statusDataField); |
82 | | - long pendingSignersOffset = unsafe.objectFieldOffset(pendingSignersField); |
83 | | - long verifiedSignersOffset = unsafe.objectFieldOffset(verifiedSignersField); |
84 | | -
|
85 | | - for(IModFileInfo mod : ModList.get().getModFiles()) { |
86 | | - SecureJar secureJar = mod.getFile().getSecureJar(); |
87 | | - if(secureJar instanceof Jar) { |
88 | | - unsafe.putObject(secureJar, statusDataOffset, LyingStatusDataMap.INSTANCE); |
89 | | - unsafe.putObject(secureJar, pendingSignersOffset, EmptyCodeSignerTable.INSTANCE); |
90 | | - unsafe.putObject(secureJar, verifiedSignersOffset, EmptyCodeSignerTable.INSTANCE); |
91 | | - } |
92 | | - } |
93 | | -
|
94 | | - */ |
| 25 | + return f; |
95 | 26 | } |
96 | 27 |
|
97 | | - static class EmptyCodeSignerTable extends Hashtable<String, CodeSigner[]> { |
98 | | - public static final EmptyCodeSignerTable INSTANCE = new EmptyCodeSignerTable(); |
99 | | - private static final CodeSigner[] VAL = new CodeSigner[0]; |
100 | | - |
101 | | - @Override |
102 | | - public synchronized CodeSigner[] put(String key, CodeSigner[] value) { |
103 | | - return null; |
104 | | - } |
105 | | - |
106 | | - @Override |
107 | | - public synchronized boolean isEmpty() { |
108 | | - return true; |
109 | | - } |
110 | | - |
111 | | - @Override |
112 | | - public synchronized boolean containsKey(Object key) { |
113 | | - return false; |
114 | | - } |
115 | | - |
116 | | - @Override |
117 | | - public synchronized CodeSigner[] get(Object key) { |
118 | | - return VAL; |
119 | | - } |
120 | | - } |
121 | | - |
122 | | - /** |
123 | | - * This map is used to replace the statusData map. |
124 | | - * |
125 | | - * The lying in containsKey is intentionally done to force certain code paths to run in Jar. |
126 | | - * Otherwise the security information might be recomputed many times. |
127 | | - */ |
128 | | - static class LyingStatusDataMap implements Map<String, Object> { |
129 | | - public static final LyingStatusDataMap INSTANCE = new LyingStatusDataMap(); |
130 | | - @Override |
131 | | - public int size() { |
132 | | - return 0; |
133 | | - } |
134 | | - |
135 | | - @Override |
136 | | - public boolean isEmpty() { |
137 | | - return false; |
138 | | - } |
139 | | - |
140 | | - @Override |
141 | | - public boolean containsKey(Object o) { |
142 | | - return true; |
143 | | - } |
144 | | - |
145 | | - @Override |
146 | | - public boolean containsValue(Object o) { |
147 | | - return false; |
148 | | - } |
149 | | - |
150 | | - @Override |
151 | | - public Object get(Object o) { |
152 | | - return null; |
153 | | - } |
154 | | - |
155 | | - @Nullable |
156 | | - @Override |
157 | | - public Object put(String s, Object o) { |
158 | | - return null; |
159 | | - } |
160 | | - |
161 | | - @Override |
162 | | - public Object remove(Object o) { |
163 | | - return null; |
164 | | - } |
165 | | - |
166 | | - @Override |
167 | | - public void putAll(@NotNull Map<? extends String, ?> map) { |
168 | | - |
169 | | - } |
170 | | - |
171 | | - @Override |
172 | | - public void clear() { |
173 | | - } |
174 | | - |
175 | | - @NotNull |
176 | | - @Override |
177 | | - public Set<String> keySet() { |
178 | | - return Collections.emptySet(); |
179 | | - } |
180 | | - |
181 | | - @NotNull |
182 | | - @Override |
183 | | - public Collection<Object> values() { |
184 | | - return Collections.emptyList(); |
185 | | - } |
186 | | - |
187 | | - @NotNull |
188 | | - @Override |
189 | | - public Set<Entry<String, Object>> entrySet() { |
190 | | - return Collections.emptySet(); |
| 28 | + private static void doClear() { |
| 29 | + Map<String, ClassInfo> classInfoCache; |
| 30 | + Field mixinField, stateField, classNodeField, methodsField, fieldsField; |
| 31 | + Class<?> stateClz; |
| 32 | + try { |
| 33 | + Field field = accessible(ClassInfo.class.getDeclaredField("cache")); |
| 34 | + classInfoCache = (Map<String, ClassInfo>) field.get(null); |
| 35 | + mixinField = accessible(ClassInfo.class.getDeclaredField("mixin")); |
| 36 | + methodsField = accessible(ClassInfo.class.getDeclaredField("methods")); |
| 37 | + fieldsField = accessible(ClassInfo.class.getDeclaredField("fields")); |
| 38 | + stateClz = Class.forName("org.spongepowered.asm.mixin.transformer.MixinInfo$State"); |
| 39 | + stateField = accessible(Class.forName("org.spongepowered.asm.mixin.transformer.MixinInfo").getDeclaredField("state")); |
| 40 | + classNodeField = accessible(stateClz.getDeclaredField("classNode")); |
| 41 | + } catch (ReflectiveOperationException | RuntimeException e) { |
| 42 | + e.printStackTrace(); |
| 43 | + return; |
191 | 44 | } |
192 | | - } |
193 | | - |
194 | | - static class EmptyAttributes extends Attributes { |
195 | | - public static final EmptyAttributes INSTANCE = new EmptyAttributes(); |
196 | | - EmptyAttributes() { |
197 | | - super(1); |
198 | | - this.map = ImmutableMap.of(); |
| 45 | + MixinEnvironment.getDefaultEnvironment().audit(); |
| 46 | + try { |
| 47 | + ClassNode emptyNode = new ClassNode(); |
| 48 | + classInfoCache.entrySet().removeIf(entry -> { |
| 49 | + if(entry.getKey().equals("java/lang/Object")) |
| 50 | + return false; |
| 51 | + ClassInfo mixinClz = entry.getValue(); |
| 52 | + try { |
| 53 | + if(mixinClz.isMixin()) { |
| 54 | + // clear classNode in MixinInfo.State |
| 55 | + IMixinInfo theInfo = (IMixinInfo) mixinField.get(mixinClz); |
| 56 | + Object state = stateField.get(theInfo); |
| 57 | + if (state != null) |
| 58 | + classNodeField.set(state, emptyNode); |
| 59 | + } |
| 60 | + // clear fields, methods |
| 61 | + ((Collection<?>)methodsField.get(mixinClz)).clear(); |
| 62 | + ((Collection<?>)fieldsField.get(mixinClz)).clear(); |
| 63 | + } catch (ReflectiveOperationException | RuntimeException e) { |
| 64 | + e.printStackTrace(); |
| 65 | + } |
| 66 | + return true; |
| 67 | + }); |
| 68 | + } catch (RuntimeException e) { |
| 69 | + e.printStackTrace(); |
199 | 70 | } |
| 71 | + ModernFix.LOGGER.warn("Cleared mixin data structures"); |
200 | 72 | } |
201 | 73 | } |
0 commit comments