|
53 | 53 | import software.coley.cafedude.classfile.constant.CpClass; |
54 | 54 | import software.coley.cafedude.classfile.constant.CpEntry; |
55 | 55 | import software.coley.cafedude.classfile.constant.CpUtf8; |
| 56 | +import software.coley.cafedude.classfile.instruction.BasicInstruction; |
| 57 | +import software.coley.cafedude.classfile.instruction.Instruction; |
| 58 | +import software.coley.cafedude.classfile.instruction.IntOperandInstruction; |
| 59 | +import software.coley.cafedude.classfile.instruction.LookupSwitchInstruction; |
| 60 | +import software.coley.cafedude.classfile.instruction.TableSwitchInstruction; |
56 | 61 | import software.coley.cafedude.io.AttributeContext; |
57 | 62 |
|
58 | 63 | import java.util.Collection; |
|
64 | 69 | import java.util.regex.Pattern; |
65 | 70 | import java.util.stream.Collectors; |
66 | 71 |
|
| 72 | +import static software.coley.cafedude.classfile.instruction.Opcodes.*; |
| 73 | + |
67 | 74 | /** |
68 | 75 | * A transformer to remove illegal attributes and data from a class. |
69 | 76 | * |
@@ -103,15 +110,70 @@ public void transform() { |
103 | 110 | clazz.getAttributes().removeIf(attribute -> !isValidWrapped(clazz, attribute)); |
104 | 111 | for (Field field : clazz.getFields()) |
105 | 112 | field.getAttributes().removeIf(attribute -> !isValidWrapped(field, attribute)); |
106 | | - for (Method method : clazz.getMethods()) |
| 113 | + for (Method method : clazz.getMethods()) { |
107 | 114 | method.getAttributes().removeIf(attribute -> !isValidWrapped(method, attribute)); |
| 115 | + CodeAttribute code = method.getAttribute(CodeAttribute.class); |
| 116 | + if (code != null) |
| 117 | + removeInvalidInstructions(code); |
| 118 | + } |
108 | 119 |
|
109 | 120 | // Record filtered CP refs, the difference of the sets are the indices that were referenced |
110 | 121 | // by removed attributes/data. |
111 | 122 | Set<CpEntry> filteredCpAccesses = clazz.cpAccesses(); |
112 | 123 | cpAccesses.removeAll(filteredCpAccesses); |
113 | 124 | } |
114 | 125 |
|
| 126 | + protected void removeInvalidInstructions(@Nonnull CodeAttribute code) { |
| 127 | + List<Instruction> instructions = code.getInstructions(); |
| 128 | + if (instructions.size() <= 1) |
| 129 | + return; |
| 130 | + int maxPc = code.computeOffsetOf(instructions.get(instructions.size() - 1)); |
| 131 | + |
| 132 | + // Remove junk try-catch entries with bogus offsets |
| 133 | + List<ExceptionTableEntry> exceptions = code.getExceptionTable(); |
| 134 | + for (int i = exceptions.size() - 1; i >= 0; i--) { |
| 135 | + ExceptionTableEntry entry = exceptions.get(i); |
| 136 | + if (entry.getStartPc() > maxPc || entry.getEndPc() > maxPc || entry.getHandlerPc() > maxPc) |
| 137 | + exceptions.remove(i); |
| 138 | + } |
| 139 | + |
| 140 | + // Remove any jump instructions that point to bogus offsets |
| 141 | + // - The code is already unverifiable, so when we stub out the instruction with |
| 142 | + // a 'return' the code is still unverifiable but fixes the ASM crash. |
| 143 | + // - The number of bytes if kept the same, so valid jump instructions elsewhere |
| 144 | + // in the method are not broken. |
| 145 | + // - This de-syncs the stack-frame entries for some reason I haven't looked into but |
| 146 | + // since the code that relies on these tricks already requires use of -noverify |
| 147 | + // I don't really feel like looking into it. Just use SKIP_FRAMES with ASM on these classes. |
| 148 | + for (int i = instructions.size() - 1; i >= 0; i--) { |
| 149 | + Instruction instruction = instructions.get(i); |
| 150 | + int op = instruction.getOpcode(); |
| 151 | + if (((op >= IFEQ && op <= JSR) || (op == GOTO_W || op == JSR_W)) && instruction instanceof IntOperandInstruction jump) { |
| 152 | + int jumpOffset = jump.getOperand(); |
| 153 | + if (jumpOffset > maxPc) { |
| 154 | + int size = instruction.computeSize(); |
| 155 | + instructions.set(i, new BasicInstruction(RETURN)); |
| 156 | + for (int j = 0; j < size - 1; j++) |
| 157 | + instructions.add(i, new BasicInstruction(NOP)); |
| 158 | + } |
| 159 | + } else if (instruction instanceof TableSwitchInstruction tswitch) { |
| 160 | + if (tswitch.getDefault() > maxPc || tswitch.getOffsets().stream().anyMatch(o -> o > maxPc)) { |
| 161 | + int size = instruction.computeSize(); |
| 162 | + instructions.set(i, new BasicInstruction(RETURN)); |
| 163 | + for (int j = 0; j < size - 1; j++) |
| 164 | + instructions.add(i, new BasicInstruction(NOP)); |
| 165 | + } |
| 166 | + } else if (instruction instanceof LookupSwitchInstruction lswitch) { |
| 167 | + if (lswitch.getDefault() > maxPc || lswitch.getOffsets().stream().anyMatch(o -> o > maxPc)) { |
| 168 | + int size = instruction.computeSize(); |
| 169 | + instructions.set(i, new BasicInstruction(RETURN)); |
| 170 | + for (int j = 0; j < size - 1; j++) |
| 171 | + instructions.add(i, new BasicInstruction(NOP)); |
| 172 | + } |
| 173 | + } |
| 174 | + } |
| 175 | + } |
| 176 | + |
115 | 177 | private void removeInvalidBootstrapMethodAttribute() { |
116 | 178 | // ASM will try to read this attribute if any CP entry exists for DYNAMIC or INVOKE_DYNAMIC. |
117 | 179 | // If no methods actually refer to those CP entries, this attribute can be filled with garbage, |
|
0 commit comments