|
54 | 54 | import org.objectweb.asm.tree.ClassNode;
|
55 | 55 | import org.objectweb.asm.tree.FieldInsnNode;
|
56 | 56 | import org.objectweb.asm.tree.FieldNode;
|
| 57 | +import org.objectweb.asm.tree.IincInsnNode; |
57 | 58 | import org.objectweb.asm.tree.InsnList;
|
58 | 59 | import org.objectweb.asm.tree.InsnNode;
|
59 | 60 | import org.objectweb.asm.tree.JumpInsnNode;
|
@@ -459,75 +460,161 @@ private void instrumentMethodEnter() {
|
459 | 460 | }
|
460 | 461 |
|
461 | 462 | // Initialize and hoist local variables to the top of the method
|
462 |
| - // if there is name conflict, do nothing for the conflicting local variable |
| 463 | + // if there is name/slot conflict, do nothing for the conflicting local variable |
463 | 464 | private Collection<LocalVariableNode> initAndHoistLocalVars(InsnList insnList) {
|
464 | 465 | if (methodNode.localVariables == null || methodNode.localVariables.isEmpty()) {
|
465 | 466 | return Collections.emptyList();
|
466 | 467 | }
|
467 | 468 | Map<String, LocalVariableNode> localVarsByName = new HashMap<>();
|
468 |
| - Set<String> duplicateNames = new HashSet<>(); |
| 469 | + Map<Integer, LocalVariableNode> localVarsBySlot = new HashMap<>(); |
| 470 | + Map<String, List<LocalVariableNode>> hoistableVarByName = new HashMap<>(); |
469 | 471 | for (LocalVariableNode localVar : methodNode.localVariables) {
|
470 | 472 | int idx = localVar.index - localVarBaseOffset;
|
471 | 473 | if (idx < argOffset) {
|
472 | 474 | // this is an argument
|
473 | 475 | continue;
|
474 | 476 | }
|
475 |
| - if (!checkDuplicateLocalVar(localVar, localVarsByName)) { |
476 |
| - duplicateNames.add(localVar.name); |
| 477 | + checkHoistableLocalVar(localVar, localVarsByName, localVarsBySlot, hoistableVarByName); |
| 478 | + } |
| 479 | + // hoist vars |
| 480 | + List<LocalVariableNode> results = new ArrayList<>(); |
| 481 | + for (Map.Entry<String, List<LocalVariableNode>> entry : hoistableVarByName.entrySet()) { |
| 482 | + List<LocalVariableNode> hoistableVars = entry.getValue(); |
| 483 | + LocalVariableNode newVarNode; |
| 484 | + if (hoistableVars.size() > 1) { |
| 485 | + // merge variables |
| 486 | + String name = hoistableVars.get(0).name; |
| 487 | + String desc = hoistableVars.get(0).desc; |
| 488 | + Type localVarType = getType(desc); |
| 489 | + int newSlot = newVar(localVarType); // new slot for the local variable |
| 490 | + newVarNode = new LocalVariableNode(name, desc, null, null, null, newSlot); |
| 491 | + Set<LabelNode> endLabels = new HashSet<>(); |
| 492 | + for (LocalVariableNode localVar : hoistableVars) { |
| 493 | + // rewrite each usage of the old var to the new slot |
| 494 | + rewriteLocalVarInsn(localVar, localVar.index, newSlot); |
| 495 | + endLabels.add(localVar.end); |
| 496 | + } |
| 497 | + hoistVar(insnList, newVarNode); |
| 498 | + newVarNode.end = findLatestLabel(methodNode.instructions, endLabels); |
| 499 | + // remove previous local variables |
| 500 | + methodNode.localVariables.removeIf(hoistableVars::contains); |
| 501 | + methodNode.localVariables.add(newVarNode); |
| 502 | + } else { |
| 503 | + // hoist the single variable and rewrite all its local var instructions |
| 504 | + newVarNode = hoistableVars.get(0); |
| 505 | + int oldIndex = newVarNode.index; |
| 506 | + newVarNode.index = newVar(getType(newVarNode.desc)); // new slot for the local variable |
| 507 | + rewriteLocalVarInsn(newVarNode, oldIndex, newVarNode.index); |
| 508 | + hoistVar(insnList, newVarNode); |
| 509 | + } |
| 510 | + results.add(newVarNode); |
| 511 | + } |
| 512 | + return results; |
| 513 | + } |
| 514 | + |
| 515 | + private LabelNode findLatestLabel(InsnList instructions, Set<LabelNode> endLabels) { |
| 516 | + for (AbstractInsnNode insn = instructions.getLast(); insn != null; insn = insn.getPrevious()) { |
| 517 | + if (insn instanceof LabelNode && endLabels.contains(insn)) { |
| 518 | + return (LabelNode) insn; |
477 | 519 | }
|
478 | 520 | }
|
479 |
| - for (String name : duplicateNames) { |
480 |
| - localVarsByName.remove(name); |
| 521 | + return null; |
| 522 | + } |
| 523 | + |
| 524 | + private void hoistVar(InsnList insnList, LocalVariableNode varNode) { |
| 525 | + LabelNode labelNode = new LabelNode(); // new start label for the local variable |
| 526 | + insnList.add(labelNode); |
| 527 | + varNode.start = labelNode; // update the start label of the local variable |
| 528 | + Type localVarType = getType(varNode.desc); |
| 529 | + addStore0Insn(insnList, varNode, localVarType); |
| 530 | + } |
| 531 | + |
| 532 | + private static void addStore0Insn( |
| 533 | + InsnList insnList, LocalVariableNode localVar, Type localVarType) { |
| 534 | + switch (localVarType.getSort()) { |
| 535 | + case Type.BOOLEAN: |
| 536 | + case Type.CHAR: |
| 537 | + case Type.BYTE: |
| 538 | + case Type.SHORT: |
| 539 | + case Type.INT: |
| 540 | + insnList.add(new InsnNode(Opcodes.ICONST_0)); |
| 541 | + break; |
| 542 | + case Type.LONG: |
| 543 | + insnList.add(new InsnNode(Opcodes.LCONST_0)); |
| 544 | + break; |
| 545 | + case Type.FLOAT: |
| 546 | + insnList.add(new InsnNode(Opcodes.FCONST_0)); |
| 547 | + break; |
| 548 | + case Type.DOUBLE: |
| 549 | + insnList.add(new InsnNode(Opcodes.DCONST_0)); |
| 550 | + break; |
| 551 | + default: |
| 552 | + insnList.add(new InsnNode(Opcodes.ACONST_NULL)); |
| 553 | + break; |
| 554 | + } |
| 555 | + insnList.add(new VarInsnNode(localVarType.getOpcode(Opcodes.ISTORE), localVar.index)); |
| 556 | + } |
| 557 | + |
| 558 | + private void checkHoistableLocalVar( |
| 559 | + LocalVariableNode localVar, |
| 560 | + Map<String, LocalVariableNode> localVarsByName, |
| 561 | + Map<Integer, LocalVariableNode> localVarsBySlot, |
| 562 | + Map<String, List<LocalVariableNode>> hoistableVarByName) { |
| 563 | + LocalVariableNode previousVarBySlot = localVarsBySlot.putIfAbsent(localVar.index, localVar); |
| 564 | + LocalVariableNode previousVarByName = localVarsByName.putIfAbsent(localVar.name, localVar); |
| 565 | + if (previousVarBySlot != null) { |
| 566 | + // there are multiple local variables with the same slot but different names |
| 567 | + // by hoisting in a new slot, we can avoid the conflict |
| 568 | + hoistableVarByName.computeIfAbsent(localVar.name, k -> new ArrayList<>()).add(localVar); |
481 | 569 | }
|
482 |
| - for (LocalVariableNode localVar : localVarsByName.values()) { |
483 |
| - Type localVarType = getType(localVar.desc); |
484 |
| - switch (localVarType.getSort()) { |
485 |
| - case Type.BOOLEAN: |
486 |
| - case Type.CHAR: |
487 |
| - case Type.BYTE: |
488 |
| - case Type.SHORT: |
489 |
| - case Type.INT: |
490 |
| - insnList.add(new InsnNode(Opcodes.ICONST_0)); |
491 |
| - break; |
492 |
| - case Type.LONG: |
493 |
| - insnList.add(new InsnNode(Opcodes.LCONST_0)); |
494 |
| - break; |
495 |
| - case Type.FLOAT: |
496 |
| - insnList.add(new InsnNode(Opcodes.FCONST_0)); |
497 |
| - break; |
498 |
| - case Type.DOUBLE: |
499 |
| - insnList.add(new InsnNode(Opcodes.DCONST_0)); |
500 |
| - break; |
501 |
| - default: |
502 |
| - insnList.add(new InsnNode(Opcodes.ACONST_NULL)); |
503 |
| - break; |
| 570 | + if (previousVarByName != null) { |
| 571 | + // there are multiple local variables with the same name |
| 572 | + // checking type to see if they are compatible |
| 573 | + Type previousType = getType(previousVarByName.desc); |
| 574 | + Type currentType = getType(localVar.desc); |
| 575 | + if (!ASMHelper.isStoreCompatibleType(previousType, currentType)) { |
| 576 | + reportWarning( |
| 577 | + "Local variable " |
| 578 | + + localVar.name |
| 579 | + + " has multiple definitions with incompatible types: " |
| 580 | + + previousVarByName.desc |
| 581 | + + " and " |
| 582 | + + localVar.desc); |
| 583 | + // remove name from hoistable |
| 584 | + hoistableVarByName.remove(localVar.name); |
| 585 | + return; |
504 | 586 | }
|
505 |
| - insnList.add(new VarInsnNode(localVarType.getOpcode(Opcodes.ISTORE), localVar.index)); |
| 587 | + // Merge variables because compatible type |
506 | 588 | }
|
507 |
| - return localVarsByName.values(); |
| 589 | + // by default, there is no conflict => hoistable |
| 590 | + hoistableVarByName.computeIfAbsent(localVar.name, k -> new ArrayList<>()).add(localVar); |
508 | 591 | }
|
509 | 592 |
|
510 |
| - private boolean checkDuplicateLocalVar( |
511 |
| - LocalVariableNode localVar, Map<String, LocalVariableNode> localVarsByName) { |
512 |
| - LocalVariableNode previousVar = localVarsByName.putIfAbsent(localVar.name, localVar); |
513 |
| - if (previousVar == null) { |
514 |
| - return true; |
| 593 | + private void rewriteLocalVarInsn(LocalVariableNode localVar, int oldSlot, int newSlot) { |
| 594 | + // previous insn could be a store to index that need to be rewritten as well |
| 595 | + AbstractInsnNode previous = localVar.start.getPrevious(); |
| 596 | + if (previous instanceof VarInsnNode) { |
| 597 | + VarInsnNode varInsnNode = (VarInsnNode) previous; |
| 598 | + if (varInsnNode.var == oldSlot) { |
| 599 | + varInsnNode.var = newSlot; |
| 600 | + } |
515 | 601 | }
|
516 |
| - // there are multiple local variables with the same name |
517 |
| - // checking type to see if they are compatible |
518 |
| - Type previousType = getType(previousVar.desc); |
519 |
| - Type currentType = getType(localVar.desc); |
520 |
| - if (!ASMHelper.isStoreCompatibleType(previousType, currentType)) { |
521 |
| - reportWarning( |
522 |
| - "Local variable " |
523 |
| - + localVar.name |
524 |
| - + " has multiple definitions with incompatible types: " |
525 |
| - + previousVar.desc |
526 |
| - + " and " |
527 |
| - + localVar.desc); |
528 |
| - return false; |
| 602 | + for (AbstractInsnNode insn = localVar.start; |
| 603 | + insn != null && insn != localVar.end; |
| 604 | + insn = insn.getNext()) { |
| 605 | + if (insn instanceof VarInsnNode) { |
| 606 | + VarInsnNode varInsnNode = (VarInsnNode) insn; |
| 607 | + if (varInsnNode.var == oldSlot) { |
| 608 | + varInsnNode.var = newSlot; |
| 609 | + } |
| 610 | + } |
| 611 | + if (insn instanceof IincInsnNode) { |
| 612 | + IincInsnNode iincInsnNode = (IincInsnNode) insn; |
| 613 | + if (iincInsnNode.var == oldSlot) { |
| 614 | + iincInsnNode.var = newSlot; |
| 615 | + } |
| 616 | + } |
529 | 617 | }
|
530 |
| - return true; |
531 | 618 | }
|
532 | 619 |
|
533 | 620 | private void createInProbeFinallyHandler(LabelNode inProbeStartLabel, LabelNode inProbeEndLabel) {
|
|
0 commit comments