Commit 755c90be849b4453c30c6c819ef4036c9a5ef6fd

Authored by Mumfrey
1 parent 7b700b7b

generate local variable table for methods when not available in the bytecode

ant/build_liteloader.xml
@@ -5,7 +5,7 @@ @@ -5,7 +5,7 @@
5 5
6 <!-- Versions !!IMPORTANT --> 6 <!-- Versions !!IMPORTANT -->
7 <property name="version" value="1.8.0_00" /> 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 <property name="author" value="Mumfrey" /> 9 <property name="author" value="Mumfrey" />
10 10
11 <!-- Project definitions and dependencies --> 11 <!-- Project definitions and dependencies -->
@@ -288,6 +288,6 @@ @@ -288,6 +288,6 @@
288 <target name="deploy" depends="production" description="Deploy artifact to local minecraft installation in APPDATA"> 288 <target name="deploy" depends="production" description="Deploy artifact to local minecraft installation in APPDATA">
289 <mkdir dir="${env.APPDATA}/.minecraft/libraries/com/mumfrey/liteloader/${mcversion}"/> 289 <mkdir dir="${env.APPDATA}/.minecraft/libraries/com/mumfrey/liteloader/${mcversion}"/>
290 <copy todir="${env.APPDATA}/.minecraft/libraries/com/mumfrey/liteloader/${mcversion}" file="${dist.dir}/${ant.project.name}-${mcversion}.${filetype}" failonerror="false" overwrite="true" /> 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 </target> 292 </target>
293 </project> 293 </project>
294 \ No newline at end of file 294 \ No newline at end of file
java/common/com/mumfrey/liteloader/transformers/ByteCodeUtilities.java
1 package com.mumfrey.liteloader.transformers; 1 package com.mumfrey.liteloader.transformers;
2 2
3 import java.lang.annotation.Annotation; 3 import java.lang.annotation.Annotation;
  4 +import java.util.ArrayList;
  5 +import java.util.HashMap;
4 import java.util.Iterator; 6 import java.util.Iterator;
5 import java.util.List; 7 import java.util.List;
  8 +import java.util.Map;
6 9
7 import org.objectweb.asm.Opcodes; 10 import org.objectweb.asm.Opcodes;
8 import org.objectweb.asm.Type; 11 import org.objectweb.asm.Type;
@@ -12,9 +15,15 @@ import org.objectweb.asm.tree.ClassNode; @@ -12,9 +15,15 @@ import org.objectweb.asm.tree.ClassNode;
12 import org.objectweb.asm.tree.FieldNode; 15 import org.objectweb.asm.tree.FieldNode;
13 import org.objectweb.asm.tree.FrameNode; 16 import org.objectweb.asm.tree.FrameNode;
14 import org.objectweb.asm.tree.InsnList; 17 import org.objectweb.asm.tree.InsnList;
  18 +import org.objectweb.asm.tree.LabelNode;
15 import org.objectweb.asm.tree.LocalVariableNode; 19 import org.objectweb.asm.tree.LocalVariableNode;
16 import org.objectweb.asm.tree.MethodNode; 20 import org.objectweb.asm.tree.MethodNode;
17 import org.objectweb.asm.tree.VarInsnNode; 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 * Utility methods for working with bytecode using ASM 29 * Utility methods for working with bytecode using ASM
@@ -23,6 +32,8 @@ import org.objectweb.asm.tree.VarInsnNode; @@ -23,6 +32,8 @@ import org.objectweb.asm.tree.VarInsnNode;
23 */ 32 */
24 public abstract class ByteCodeUtilities 33 public abstract class ByteCodeUtilities
25 { 34 {
  35 + private static Map<String, List<LocalVariableNode>> calculatedLocalVariables = new HashMap<String, List<LocalVariableNode>>();
  36 +
26 private ByteCodeUtilities() {} 37 private ByteCodeUtilities() {}
27 38
28 /** 39 /**
@@ -236,8 +247,9 @@ public abstract class ByteCodeUtilities @@ -236,8 +247,9 @@ public abstract class ByteCodeUtilities
236 LocalVariableNode localVariableNode = null; 247 LocalVariableNode localVariableNode = null;
237 248
238 int pos = method.instructions.indexOf(node); 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 if (local.index != var) continue; 254 if (local.index != var) continue;
243 int start = method.instructions.indexOf(local.start); 255 int start = method.instructions.indexOf(local.start);
@@ -250,8 +262,141 @@ public abstract class ByteCodeUtilities @@ -250,8 +262,141 @@ public abstract class ByteCodeUtilities
250 262
251 return localVariableNode; 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 * Get the source code name for the specified type 400 * Get the source code name for the specified type
256 * 401 *
257 * @param type 402 * @param type
java/common/com/mumfrey/liteloader/transformers/event/EventTransformer.java
@@ -131,7 +131,7 @@ public final class EventTransformer extends ClassTransformer @@ -131,7 +131,7 @@ public final class EventTransformer extends ClassTransformer
131 { 131 {
132 if (injectionPoint.captureLocals != this.captureLocals) 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,7 +285,7 @@ public final class EventTransformer extends ClassTransformer
285 int startPos = ByteCodeUtilities.getFirstNonArgLocalIndex(method); 285 int startPos = ByteCodeUtilities.getFirstNonArgLocalIndex(method);
286 286
287 LiteLoaderLogger.debug(ClassTransformer.HORIZONTAL_RULE); 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 for (int i = startPos; i < locals.length; i++) 289 for (int i = startPos; i < locals.length; i++)
290 { 290 {
291 LocalVariableNode local = locals[i]; 291 LocalVariableNode local = locals[i];