/*
* Copyright 2016 Cel Skeggs.
*
* This file is part of the CCRE, the Common Chicken Runtime Engine.
*
* The CCRE is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* The CCRE is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the CCRE. If not, see <http://www.gnu.org/licenses/>.
*/
package ccre.verifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Consumer;
import ccre.drivers.ByteFiddling;
import ccre.verifier.ClassParser.CPInfo;
import ccre.verifier.ClassParser.ClassFile;
import ccre.verifier.ClassParser.ExceptionHandlerInfo;
import ccre.verifier.ClassParser.MethodInfo;
class BytecodeParser {
private final byte[] code;
private final ExceptionHandlerInfo[] handlers;
private final int[] block_begins;
private final ClassFile cf;
private final MethodInfo method;
public BytecodeParser(ClassParser.MethodInfo method) throws ClassFormatException {
if (method.code == null || method.handlers == null) {
throw new IllegalArgumentException("Cannot use method without bytecode: " + method);
}
this.method = method;
this.code = method.code;
this.handlers = method.handlers;
this.cf = method.declaringClass;
this.block_begins = scanBlocks();
}
private int[] scanBlocks() throws ClassFormatException {
ArrayList<Integer> blocks = new ArrayList<>();
blocks.add(0);
for (ExceptionHandlerInfo info : handlers) {
if (!blocks.contains(info.handler_pc)) {
blocks.add(info.handler_pc);
}
}
for (int i = 0; i < blocks.size(); i++) {
scanFromBlock(blocks.get(i), (i2) -> {
if (!blocks.contains(i2)) {
blocks.add(i2);
}
});
}
int[] out = new int[blocks.size()];
for (int i = 0; i < out.length; i++) {
out[i] = blocks.get(i);
}
return out;
}
private void scanFromBlock(int i, Consumer<Integer> out) throws ClassFormatException {
try {
while (true) {
int opcode_offset = i;
int op = code[i++] & 0xFF;
if (op >= 0x00 && op <= 0x0F) {
// single-byte simple constant instructions
} else if (op >= 0x15 && op <= 0x19) {
i++; // variable load instructions
} else if (op >= 0x1A && op <= 0x35) {
// single-byte fixed-load instructions and array-load
// instructions
} else if (op >= 0x36 && op <= 0x3A) {
i++; // variable store instructions
} else if (op >= 0x3B && op <= 0x83) {
// single-byte fixed-store instructions, array-store
// instructions, stack manipulation instructions, and math
// instructions
} else if (op >= 0x85 && op <= 0x98) {
// single-byte conversion instructions and non-branching
// comparison instructions
} else if ((op >= 0x99 && op <= 0xA7) || (op >= 0xC6 && op <= 0xC7)) {
// ^^^^^^^^^^^^--- INCLUDES ABNORMAL CONDITIONS
// conditional branch instructions, goto, jsr
int branchbyte1 = code[i++] & 0xFF;
int branchbyte2 = code[i++] & 0xFF;
out.accept(opcode_offset + (short) ((branchbyte1 << 8) | branchbyte2));
if (op == 0xA7) { // goto
return; // no more code in this block
}
} else if (op >= 0xAC && op <= 0xB1) {
// method return instructions
return; // no more code in this block
} else if (op >= 0xB2 && op <= 0xB8) {
// field access instructions and some invocation
// instructions
i += 2;
} else if (op >= 0xB9 && op <= 0xBA) {
// some invocation instructions
i += 4;
} else if (op >= 0xC0 && op <= 0xC1) {
i += 2;
} else if (op >= 0xC2 && op <= 0xC3) {
// no arguments for these
} else {
switch (op) {
case 0x10: // bipush
case 0x12: // ldc
case 0xBC: // newarray
i++;
break;
case 0x11: // sipush
case 0x13: // ldc_w
case 0x14: // ldc2_w
case 0x84: // iinc
case 0xBB: // new
case 0xBD: // anewarray
i += 2;
break;
case 0xA9: // ret
// this is a jsr ret, which returns to after the jsr,
// which we've already looked at
i++;
return; // no more code in this block
case 0xAA: { // tableswitch
while (i % 4 != 0) {
i++;
}
int default_int = ByteFiddling.asInt32BE(code, i);
int low_int = ByteFiddling.asInt32BE(code, i + 4);
int high_int = ByteFiddling.asInt32BE(code, i + 8);
i += 12;
out.accept(default_int + opcode_offset);
for (int j = low_int; j <= high_int; j++) {
out.accept(ByteFiddling.asInt32BE(code, i) + opcode_offset);
i += 4;
}
break;
}
case 0xAB: { // lookupswitch
while (i % 4 != 0) {
i++;
}
int default_int = ByteFiddling.asInt32BE(code, i);
int npairs_int = ByteFiddling.asInt32BE(code, i + 4);
i += 8;
out.accept(default_int + opcode_offset);
for (int j = 0; j < npairs_int; j++) {
// ignore match of ByteFiddling.asInt32BE(code, i)
out.accept(ByteFiddling.asInt32BE(code, i + 4) + opcode_offset);
i += 8;
}
break;
}
case 0xBE: // arraylength
// no arguments
break;
case 0xBF: // athrow
// no arguments; end block because throw
return;
case 0xC4: { // wide
op = code[i++] & 0xFF;
if ((op >= 0x15 && op <= 0x19) || (op >= 0x36 && op <= 0x3A) || op == 0xA9) {
// wide load or save, or ret
i += 2;
// in case of ret, nothing special needed because
// that was handled at the jsr
} else if (op == 0x84) {
i += 4;
} else {
throw new ClassFormatException("Invalid wide opcode in bytecode: " + op);
}
break;
}
case 0xC5: // multianewarray
i += 3;
break;
case 0xC8: // goto_w
case 0xC9: { // jsr_w
int branchbyte1 = code[i++] & 0xFF;
int branchbyte2 = code[i++] & 0xFF;
int branchbyte3 = code[i++] & 0xFF;
int branchbyte4 = code[i++] & 0xFF;
out.accept(((branchbyte1 << 24) | (branchbyte2 << 16) | (branchbyte3 << 8) | branchbyte4) + opcode_offset);
if (op == 0xC8) {
return;
}
break;
}
default:
throw new ClassFormatException("Invalid opcode in bytecode: " + op + " at " + opcode_offset + " in " + this.method);
}
}
}
} catch (ArrayIndexOutOfBoundsException ex) {
throw new ClassFormatException("Invalid bytecode", ex);
}
}
public static class ReferenceInfo {
public CPInfo target;
public int bytecode_index;
public MethodInfo method;
public String getSourceFile() throws ClassFormatException {
return method.declaringClass.getSourceFile();
}
public int getLineNumber() {
return method.getLineNumberFor(bytecode_index);
}
}
public int getReferenceCount() throws ClassFormatException {
int out = 0;
// TODO: clean this up
ArrayList<ReferenceInfo> outa = new ArrayList<>();
for (int block : block_begins) {
out += this.getBlockReferences(block, outa);
}
return out + outa.size();
}
public ReferenceInfo[] getReferences() throws ClassFormatException {
ArrayList<ReferenceInfo> out = new ArrayList<>();
for (int block : block_begins) {
this.getBlockReferences(block, out);
}
return out.toArray(new ReferenceInfo[out.size()]);
}
// returns count of other references
private int getBlockReferences(int i, Collection<ReferenceInfo> references) throws ClassFormatException {
int other_refs = 0;
try {
while (true) {
int original_offset = i;
int op = code[i++] & 0xFF;
if (op >= 0x00 && op <= 0x0F) {
// single-byte simple constant instructions
} else if (op >= 0x15 && op <= 0x19) {
i++; // variable load instructions
} else if (op >= 0x1A && op <= 0x35) {
// single-byte fixed-load instructions and array-load
// instructions
} else if (op >= 0x36 && op <= 0x3A) {
i++; // variable store instructions
} else if (op >= 0x3B && op <= 0x83) {
// single-byte fixed-store instructions, array-store
// instructions, stack manipulation instructions, and math
// instructions
} else if (op >= 0x85 && op <= 0x98) {
// single-byte conversion instructions and non-branching
// comparison instructions
} else if ((op >= 0x99 && op <= 0xA6) || (op >= 0xC6 && op <= 0xC7)) {
i += 2;
} else if (op >= 0xAC && op <= 0xB1) {
// method return instructions
return other_refs; // no more code in this block
} else if (op >= 0xB2 && op <= 0xB5) {
// field access instructions
i += 2;
other_refs++;
} else if (op == 0xB6) {
int indexbyte1 = code[i++] & 0xFF;
int indexbyte2 = code[i++] & 0xFF;
int index = (indexbyte1 << 8) | indexbyte2;
ReferenceInfo refInfo = new ReferenceInfo();
refInfo.target = cf.getConst(index);
refInfo.target.requireTag(ClassParser.CONSTANT_Methodref);
refInfo.method = method;
refInfo.bytecode_index = original_offset;
references.add(refInfo);
} else if (op >= 0xB7 && op <= 0xB8) {
int indexbyte1 = code[i++] & 0xFF;
int indexbyte2 = code[i++] & 0xFF;
int index = (indexbyte1 << 8) | indexbyte2;
ReferenceInfo refInfo = new ReferenceInfo();
refInfo.target = cf.getConst(index);
refInfo.target.requireTagOf(ClassParser.CONSTANT_Methodref, ClassParser.CONSTANT_InterfaceMethodref);
refInfo.method = method;
refInfo.bytecode_index = original_offset;
references.add(refInfo);
} else if (op == 0xB9) {
int indexbyte1 = code[i++] & 0xFF;
int indexbyte2 = code[i++] & 0xFF;
int index = (indexbyte1 << 8) | indexbyte2;
ReferenceInfo refInfo = new ReferenceInfo();
refInfo.target = cf.getConst(index);
refInfo.target.requireTag(ClassParser.CONSTANT_InterfaceMethodref);
refInfo.method = method;
refInfo.bytecode_index = original_offset;
references.add(refInfo);
i += 2;
} else if (op == 0xBA) {
int indexbyte1 = code[i++] & 0xFF;
int indexbyte2 = code[i++] & 0xFF;
int index = (indexbyte1 << 8) | indexbyte2;
ReferenceInfo refInfo = new ReferenceInfo();
refInfo.target = cf.getConst(index);
refInfo.target.requireTag(ClassParser.CONSTANT_InvokeDynamic);
refInfo.method = method;
refInfo.bytecode_index = original_offset;
references.add(refInfo);
i += 2;
} else if (op >= 0xC0 && op <= 0xC1) {
i += 2;
} else if (op >= 0xC2 && op <= 0xC3) {
// no arguments for these
} else {
switch (op) {
case 0x10: // bipush
case 0x12: // ldc
i++;
break;
case 0xBC: // newarray
i++;
other_refs++;
break;
case 0x11: // sipush
case 0x13: // ldc_w
case 0x14: // ldc2_w
case 0x84: // iinc
i += 2;
break;
case 0xBB: // new
case 0xBD: // anewarray
i += 2;
other_refs++;
break;
case 0xA9: // ret
// this is a jsr ret, which returns to after the jsr,
// which we've already looked at
i++;
return other_refs; // no more code in this block
case 0xAA: { // tableswitch
while (i % 4 != 0) {
i++;
}
int low_int = ByteFiddling.asInt32BE(code, i + 4);
int high_int = ByteFiddling.asInt32BE(code, i + 8);
i += 16 + 4 * (high_int - low_int);
break;
}
case 0xAB: { // lookupswitch
while (i % 4 != 0) {
i++;
}
int npairs_int = ByteFiddling.asInt32BE(code, i + 4);
i += 8 + 8 * npairs_int;
break;
}
case 0xBE: // arraylength
// no arguments
break;
case 0xA7: // goto
case 0xBF: // athrow
// no arguments; end block because throw
return other_refs;
case 0xC4: { // wide
op = code[i++] & 0xFF;
if ((op >= 0x15 && op <= 0x19) || (op >= 0x36 && op <= 0x3A) || op == 0xA9) {
// wide load or save, or ret
i += 2;
// in case of ret, nothing special needed because
// that was handled at the jsr
} else if (op == 0x84) {
i += 4;
} else {
throw new ClassFormatException("Invalid wide opcode in bytecode: " + op);
}
break;
}
case 0xC5: // multianewarray
i += 3;
other_refs++;
break;
case 0xC8: // goto_w
i += 4;
return other_refs;
case 0xC9: { // jsr_w
i += 4;
break;
}
default:
throw new ClassFormatException("Invalid opcode in bytecode: " + op);
}
}
}
} catch (ArrayIndexOutOfBoundsException ex) {
throw new ClassFormatException("Invalid bytecode", ex);
}
}
}