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