Commit 755c90be849b4453c30c6c819ef4036c9a5ef6fd
1 parent
7b700b7b
generate local variable table for methods when not available in the bytecode
Showing
3 changed files
with
151 additions
and
6 deletions
ant/build_liteloader.xml
... | ... | @@ -5,7 +5,7 @@ |
5 | 5 | |
6 | 6 | <!-- Versions !!IMPORTANT --> |
7 | 7 | <property name="version" value="1.8.0_00" /> |
8 | - <property name="mcversion" value="1.8.00" /> | |
8 | + <property name="mcversion" value="1.8" /> | |
9 | 9 | <property name="author" value="Mumfrey" /> |
10 | 10 | |
11 | 11 | <!-- Project definitions and dependencies --> |
... | ... | @@ -288,6 +288,6 @@ |
288 | 288 | <target name="deploy" depends="production" description="Deploy artifact to local minecraft installation in APPDATA"> |
289 | 289 | <mkdir dir="${env.APPDATA}/.minecraft/libraries/com/mumfrey/liteloader/${mcversion}"/> |
290 | 290 | <copy todir="${env.APPDATA}/.minecraft/libraries/com/mumfrey/liteloader/${mcversion}" file="${dist.dir}/${ant.project.name}-${mcversion}.${filetype}" failonerror="false" overwrite="true" /> |
291 | - <copy todir="${eclipse}/Installer/src/main/resources" file="${dist.dir}/${ant.project.name}-${mcversion}.${filetype}" failonerror="false" overwrite="true" /> | |
291 | + <copy todir="${eclipse}/LiteLoaderInstaller/src/main/resources" file="${dist.dir}/${ant.project.name}-${mcversion}.${filetype}" failonerror="false" overwrite="true" /> | |
292 | 292 | </target> |
293 | 293 | </project> |
294 | 294 | \ No newline at end of file | ... | ... |
java/common/com/mumfrey/liteloader/transformers/ByteCodeUtilities.java
1 | 1 | package com.mumfrey.liteloader.transformers; |
2 | 2 | |
3 | 3 | import java.lang.annotation.Annotation; |
4 | +import java.util.ArrayList; | |
5 | +import java.util.HashMap; | |
4 | 6 | import java.util.Iterator; |
5 | 7 | import java.util.List; |
8 | +import java.util.Map; | |
6 | 9 | |
7 | 10 | import org.objectweb.asm.Opcodes; |
8 | 11 | import org.objectweb.asm.Type; |
... | ... | @@ -12,9 +15,15 @@ import org.objectweb.asm.tree.ClassNode; |
12 | 15 | import org.objectweb.asm.tree.FieldNode; |
13 | 16 | import org.objectweb.asm.tree.FrameNode; |
14 | 17 | import org.objectweb.asm.tree.InsnList; |
18 | +import org.objectweb.asm.tree.LabelNode; | |
15 | 19 | import org.objectweb.asm.tree.LocalVariableNode; |
16 | 20 | import org.objectweb.asm.tree.MethodNode; |
17 | 21 | import org.objectweb.asm.tree.VarInsnNode; |
22 | +import org.objectweb.asm.tree.analysis.Analyzer; | |
23 | +import org.objectweb.asm.tree.analysis.AnalyzerException; | |
24 | +import org.objectweb.asm.tree.analysis.BasicValue; | |
25 | +import org.objectweb.asm.tree.analysis.Frame; | |
26 | +import org.objectweb.asm.tree.analysis.SimpleVerifier; | |
18 | 27 | |
19 | 28 | /** |
20 | 29 | * Utility methods for working with bytecode using ASM |
... | ... | @@ -23,6 +32,8 @@ import org.objectweb.asm.tree.VarInsnNode; |
23 | 32 | */ |
24 | 33 | public abstract class ByteCodeUtilities |
25 | 34 | { |
35 | + private static Map<String, List<LocalVariableNode>> calculatedLocalVariables = new HashMap<String, List<LocalVariableNode>>(); | |
36 | + | |
26 | 37 | private ByteCodeUtilities() {} |
27 | 38 | |
28 | 39 | /** |
... | ... | @@ -236,8 +247,9 @@ public abstract class ByteCodeUtilities |
236 | 247 | LocalVariableNode localVariableNode = null; |
237 | 248 | |
238 | 249 | int pos = method.instructions.indexOf(node); |
239 | - | |
240 | - for (LocalVariableNode local : method.localVariables) | |
250 | + | |
251 | + List<LocalVariableNode> localVariables = ByteCodeUtilities.getLocalVariableTable(classNode, method); | |
252 | + for (LocalVariableNode local : localVariables) | |
241 | 253 | { |
242 | 254 | if (local.index != var) continue; |
243 | 255 | int start = method.instructions.indexOf(local.start); |
... | ... | @@ -250,8 +262,141 @@ public abstract class ByteCodeUtilities |
250 | 262 | |
251 | 263 | return localVariableNode; |
252 | 264 | } |
265 | + | |
266 | + /** | |
267 | + * Fetches or generates the local variable table for the specified method. Since Mojang strip the local variable table | |
268 | + * as part of the obfuscation process, we need to generate the local variable table when running obfuscated. We cache | |
269 | + * the generated tables so that we only need to do the relatively expensive calculation once per method we encounter. | |
270 | + * | |
271 | + * @param classNode | |
272 | + * @param method | |
273 | + * @return | |
274 | + */ | |
275 | + public static List<LocalVariableNode> getLocalVariableTable(ClassNode classNode, MethodNode method) | |
276 | + { | |
277 | + if (method.localVariables.isEmpty()) | |
278 | + { | |
279 | + String signature = String.format("%s.%s%s", classNode.name, method.name, method.desc); | |
280 | + | |
281 | + List<LocalVariableNode> localVars = ByteCodeUtilities.calculatedLocalVariables.get(signature); | |
282 | + if (localVars != null) | |
283 | + { | |
284 | + return localVars; | |
285 | + } | |
286 | + | |
287 | + localVars = ByteCodeUtilities.generateLocalVariableTable(classNode, method); | |
288 | + ByteCodeUtilities.calculatedLocalVariables.put(signature, localVars); | |
289 | + return localVars; | |
290 | + } | |
291 | + | |
292 | + return method.localVariables; | |
293 | + } | |
253 | 294 | |
254 | 295 | /** |
296 | + * Use ASM Analyzer to generate the local variable table for the specified method | |
297 | + * | |
298 | + * @param classNode | |
299 | + * @param method | |
300 | + * @return | |
301 | + */ | |
302 | + public static List<LocalVariableNode> generateLocalVariableTable(ClassNode classNode, MethodNode method) | |
303 | + { | |
304 | + // Use Analyzer to generate the bytecode frames | |
305 | + Analyzer<BasicValue> analyzer = new Analyzer<BasicValue>(new SimpleVerifier(Type.getObjectType(classNode.name), null, null, false)); | |
306 | + try | |
307 | + { | |
308 | + analyzer.analyze(classNode.name, method); | |
309 | + } | |
310 | + catch (AnalyzerException ex) | |
311 | + { | |
312 | + ex.printStackTrace(); | |
313 | + } | |
314 | + | |
315 | + // Get frames from the Analyzer | |
316 | + Frame<BasicValue>[] frames = analyzer.getFrames(); | |
317 | + | |
318 | + // Record the original size of hte method | |
319 | + int methodSize = method.instructions.size(); | |
320 | + | |
321 | + // List of LocalVariableNodes to return | |
322 | + List<LocalVariableNode> localVariables = new ArrayList<LocalVariableNode>(); | |
323 | + | |
324 | + LocalVariableNode[] localNodes = new LocalVariableNode[method.maxLocals]; // LocalVariableNodes for current frame | |
325 | + BasicValue[] locals = new BasicValue[method.maxLocals]; // locals in previous frame, used to work out what changes between frames | |
326 | + LabelNode[] labels = new LabelNode[methodSize]; // Labels to add to the method, for the markers | |
327 | + | |
328 | + // Traverse the frames and work out when locals begin and end | |
329 | + for (int i = 0; i < methodSize; i++) | |
330 | + { | |
331 | + Frame<BasicValue> f = frames[i]; | |
332 | + if (f == null) continue; | |
333 | + LabelNode label = null; | |
334 | + | |
335 | + for (int j = 0; j < f.getLocals(); j++) | |
336 | + { | |
337 | + BasicValue local = f.getLocal(j); | |
338 | + if (local == null && locals[j] == null) continue; | |
339 | + if (local != null && local.equals(locals[j])) continue; | |
340 | + | |
341 | + if (label == null) | |
342 | + { | |
343 | + label = new LabelNode(); | |
344 | + labels[i] = label; | |
345 | + } | |
346 | + | |
347 | + if (local == null && locals[j] != null) | |
348 | + { | |
349 | + localVariables.add(localNodes[j]); | |
350 | + localNodes[j].end = label; | |
351 | + localNodes[j] = null; | |
352 | + } | |
353 | + else if (local != null) | |
354 | + { | |
355 | + if (locals[j] != null) | |
356 | + { | |
357 | + localVariables.add(localNodes[j]); | |
358 | + localNodes[j].end = label; | |
359 | + localNodes[j] = null; | |
360 | + } | |
361 | + | |
362 | + String desc = (local.getType() != null) ? local.getType().getDescriptor() : null; | |
363 | + localNodes[j] = new LocalVariableNode("var" + j, desc, null, label, null, j); | |
364 | + } | |
365 | + | |
366 | + locals[j] = local; | |
367 | + } | |
368 | + } | |
369 | + | |
370 | + // Reached the end of the method so flush all current locals and mark the end | |
371 | + LabelNode label = null; | |
372 | + for (int k = 0; k < localNodes.length; k++) | |
373 | + { | |
374 | + if (localNodes[k] != null) | |
375 | + { | |
376 | + if (label == null) | |
377 | + { | |
378 | + label = new LabelNode(); | |
379 | + method.instructions.add(label); | |
380 | + } | |
381 | + | |
382 | + localNodes[k].end = label; | |
383 | + localVariables.add(localNodes[k]); | |
384 | + } | |
385 | + } | |
386 | + | |
387 | + // Insert generated labels into the method body | |
388 | + for (int n = methodSize - 1; n >= 0; n--) | |
389 | + { | |
390 | + if (labels[n] != null) | |
391 | + { | |
392 | + method.instructions.insert(method.instructions.get(n), labels[n]); | |
393 | + } | |
394 | + } | |
395 | + | |
396 | + return localVariables; | |
397 | + } | |
398 | + | |
399 | + /** | |
255 | 400 | * Get the source code name for the specified type |
256 | 401 | * |
257 | 402 | * @param type | ... | ... |
java/common/com/mumfrey/liteloader/transformers/event/EventTransformer.java
... | ... | @@ -131,7 +131,7 @@ public final class EventTransformer extends ClassTransformer |
131 | 131 | { |
132 | 132 | if (injectionPoint.captureLocals != this.captureLocals) |
133 | 133 | { |
134 | - throw new RuntimeException("Overlapping injection points defined with incompatible settings. Attempting to handle " + injectionPoint.getClass().getSimpleName() + " with capture locals [" + injectionPoint.captureLocals + "] but already defined injection point with [" + this.captureLocals + "]"); | |
134 | + throw new RuntimeException("Overlapping injection points defined with incompatible settings. Attempting to handle " + injectionPoint + " with capture locals [" + injectionPoint.captureLocals + "] but already defined injection point with [" + this.captureLocals + "]"); | |
135 | 135 | } |
136 | 136 | } |
137 | 137 | |
... | ... | @@ -285,7 +285,7 @@ public final class EventTransformer extends ClassTransformer |
285 | 285 | int startPos = ByteCodeUtilities.getFirstNonArgLocalIndex(method); |
286 | 286 | |
287 | 287 | LiteLoaderLogger.debug(ClassTransformer.HORIZONTAL_RULE); |
288 | - LiteLoaderLogger.debug("Logging local variables for " + injectionPoint.getClass().getSimpleName()); | |
288 | + LiteLoaderLogger.debug("Logging local variables for " + injectionPoint); | |
289 | 289 | for (int i = startPos; i < locals.length; i++) |
290 | 290 | { |
291 | 291 | LocalVariableNode local = locals[i]; | ... | ... |