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,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]; |