mockit.external.asm.MethodReader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jmockit Show documentation
Show all versions of jmockit Show documentation
JMockit is a Java toolkit for automated developer testing.
It contains APIs for the creation of the objects to be tested, for mocking dependencies, and for faking external
APIs; JUnit (4 & 5) and TestNG test runners are supported.
It also contains an advanced code coverage tool.
package mockit.external.asm;
import static mockit.external.asm.MethodReader.InstructionType.*;
import static mockit.external.asm.MethodReader.InstructionType.IINC;
import static mockit.external.asm.Opcodes.*;
final class MethodReader extends AnnotatedReader
{
interface InstructionType
{
int NOARG = 0; // instructions without any argument
int SBYTE = 1; // instructions with a signed byte argument
int SHORT = 2; // instructions with a signed short argument
int VAR = 3; // instructions with a local variable index argument
int IMPLVAR = 4; // instructions with an implicit local variable index argument
int TYPE = 5; // instructions with a type descriptor argument
int FIELDORMETH = 6; // field and method invocations instructions
int ITFMETH = 7; // INVOKEINTERFACE/INVOKEDYNAMIC instruction
int INDYMETH = 8; // INVOKEDYNAMIC instruction
int LABEL = 9; // instructions with a 2 bytes bytecode offset label
int LABELW = 10; // instructions with a 4 bytes bytecode offset label
int LDC = 11; // the LDC instruction
int LDCW = 12; // the LDC_W and LDC2_W instructions
int IINC = 13; // the IINC instruction
int TABL = 14; // the TABLESWITCH instruction
int LOOK = 15; // the LOOKUPSWITCH instruction
@SuppressWarnings("unused") int MANA_INSN = 16; // the MULTIANEWARRAY instruction
int WIDE = 17; // the WIDE instruction
}
/**
* The instruction types of all JVM opcodes.
*/
private static final byte[] TYPE = new byte[220];
static {
String s =
"AAAAAAAAAAAAAAAABCLMMDDDDDEEEEEEEEEEEEEEEEEEEEAAAAAAAADD" +
"DDDEEEEEEEEEEEEEEEEEEEEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +
"AAAAAAAAAAAAAAAAANAAAAAAAAAAAAAAAAAAAAJJJJJJJJJJJJJJJJDOPAA" +
"AAAAGGGGGGGHIFBFAAFFAARQJJKKJJJJJJJJJJJJJJJJJJ";
byte[] b = TYPE;
for (int i = 0; i < b.length; ++i) {
b[i] = (byte) (s.charAt(i) - 'A');
}
}
private final ClassReader cr;
private String[] exceptions;
MethodReader(ClassReader cr) { super(cr); this.cr = cr; }
/**
* Reads a method and makes the given visitor visit it.
*
* @param context information about the class being parsed.
* @param u the start offset of the method in the class file.
* @return the offset of the first byte following the method in the class.
*/
int readMethod(Context context, int u) {
u = readMethodDeclaration(context, u);
int u0 = u;
int code = 0;
int exception = 0;
exceptions = null;
String signature = null;
int anns = 0;
int annDefault = 0;
int paramAnns = 0;
char[] c = context.buffer;
for (int i = readUnsignedShort(u); i > 0; --i) {
String attrName = readUTF8(u + 2, c);
if ("Code".equals(attrName)) {
code = u + 8;
}
else if ("Exceptions".equals(attrName)) {
exception = readExceptionsInThrowsClause(u, c);
}
else if ("Signature".equals(attrName)) {
signature = readUTF8(u + 8, c);
}
else if ("Deprecated".equals(attrName)) {
context.access = Access.asDeprecated(context.access);
}
else if ("RuntimeVisibleAnnotations".equals(attrName)) {
anns = u + 8;
}
else if ("AnnotationDefault".equals(attrName)) {
annDefault = u + 8;
}
else if ("Synthetic".equals(attrName)) {
context.access = Access.asSynthetic(context.access);
}
else if ("RuntimeVisibleParameterAnnotations".equals(attrName)) {
paramAnns = u + 8;
}
u += 6 + readInt(u + 4);
}
u += 2;
readMethodBody(context, u0, u, code, exception, signature, anns, annDefault, paramAnns);
return u;
}
private int readMethodDeclaration(Context context, int u) {
char[] c = context.buffer;
context.access = readUnsignedShort(u);
context.name = readUTF8(u + 2, c);
context.desc = readUTF8(u + 4, c);
return u + 6;
}
private int readExceptionsInThrowsClause(int u, char[] c) {
int n = readUnsignedShort(u + 8);
exceptions = new String[n];
int exception = u + 10;
for (int j = 0; j < n; ++j) {
exceptions[j] = readClass(exception, c);
exception += 2;
}
return exception;
}
private void readMethodBody(
Context context, int u0, int u, int code, int exception, String signature, int anns, int annDefault, int paramAnns
) {
MethodVisitor mv = cr.cv.visitMethod(context.access, context.name, context.desc, signature, exceptions);
if (mv == null || copyMethodBodyIfApplicable(mv, u, exception, signature, u0)) {
return;
}
char[] c = context.buffer;
readAnnotationDefaultValue(mv, c, annDefault);
readAnnotationValues(mv, c, anns);
readParameterAnnotations(mv, context, paramAnns);
readMethodCode(mv, context, code);
mv.visitEnd();
}
/**
* If the returned MethodVisitor is in fact a MethodWriter, it means there is no method adapter between the reader
* and the writer.
* If, in addition, the writer's constant pool was copied from this reader (mw.cw.cr == this), and the signature
* and exceptions of the method have not been changed, then it is possible to skip all visit events and just copy
* the original code of the method to the writer (the access, name and descriptor can have been changed, this is not
* important since they are not copied as is from the reader).
*/
private boolean copyMethodBodyIfApplicable(
MethodVisitor mv, int u, int exception, String signature, int firstAttribute
) {
if (mv instanceof MethodWriter) {
MethodWriter mw = (MethodWriter) mv;
//noinspection StringEquality
if (mw.cw.cr == cr && signature == mw.signature) {
boolean sameExceptions = false;
ThrowsClause throwsClause = mw.throwsClause;
int exceptionCount = throwsClause.getExceptionCount();
if (exceptions == null) {
sameExceptions = exceptionCount == 0;
}
else if (exceptions.length == exceptionCount) {
sameExceptions = true;
for (int j = exceptions.length - 1; j >= 0; --j) {
exception -= 2;
int exceptionIndex = readUnsignedShort(exception);
if (throwsClause.getExceptionIndex(j) != exceptionIndex) {
sameExceptions = false;
break;
}
}
}
if (sameExceptions) {
// We do not copy directly the code into MethodWriter to save a byte array copy operation.
// The real copy will be done in ClassWriter.toByteArray().
mw.classReaderOffset = firstAttribute;
mw.classReaderLength = u - firstAttribute;
return true;
}
}
}
return false;
}
private void readAnnotationDefaultValue(MethodVisitor mv, char[] c, int annotationDefault) {
if (annotationDefault != 0) {
AnnotationVisitor dv = mv.visitAnnotationDefault();
annotationReader.readAnnotationValue(annotationDefault, c, null, dv);
if (dv != null) {
dv.visitEnd();
}
}
}
private void readAnnotationValues(MethodVisitor mv, char[] c, int anns) {
if (anns != 0) {
for (int i = readUnsignedShort(anns), v = anns + 2; i > 0; i--) {
String desc = readUTF8(v, c);
AnnotationVisitor av = mv.visitAnnotation(desc);
v = annotationReader.readAnnotationValues(v + 2, c, true, av);
}
}
}
/**
* Reads parameter annotations and makes the given visitor visit them.
*
* @param mv the visitor that must visit the annotations.
* @param context information about the class being parsed.
* @param v start offset in {@link #b} of the annotations to be read.
*/
private void readParameterAnnotations(MethodVisitor mv, Context context, int v) {
if (v != 0) {
char[] c = context.buffer;
int parameters = b[v++] & 0xFF;
AnnotationVisitor av;
for (int i = 0; i < parameters; i++) {
int j = readUnsignedShort(v);
v += 2;
for (; j > 0; j--) {
String desc = readUTF8(v, c);
av = mv.visitParameterAnnotation(i, desc);
v = annotationReader.readAnnotationValues(v + 2, c, true, av);
}
}
}
}
private void readMethodCode(MethodVisitor mv, Context context, int code) {
if (code != 0 && context.readCode()) {
mv.visitCode();
readCode(mv, context, code);
}
}
/**
* Reads the bytecode of a method and makes the given visitor visit it.
*
* @param mv the visitor that must visit the method's code.
* @param context information about the class being parsed.
* @param u the start offset of the code attribute in the class file.
*/
private void readCode(MethodVisitor mv, Context context, int u) {
// Reads the header.
char[] c = context.buffer;
int maxStack = readUnsignedShort(u);
int codeLength = readInt(u + 4);
u += 8;
// Reads the bytecode to find the labels.
int codeStart = u;
int codeEnd = u + codeLength;
Label[] labels = new Label[codeLength + 2];
context.labels = labels;
u = readAllLabelsInCodeBlock(context, u, codeLength, codeStart, codeEnd);
// Reads the try catch entries to find the labels, and also visits them.
u = readTryCatchBlocks(mv, context, u);
u += 2;
// Reads the code attributes.
int varTable = 0;
int varTypeTable = 0;
for (int i = readUnsignedShort(u); i > 0; i--) {
String attrName = readUTF8(u + 2, c);
if ("LocalVariableTable".equals(attrName)) {
varTable = readLocalVariableTable(context, u, varTable);
}
else if ("LocalVariableTypeTable".equals(attrName)) {
varTypeTable = u + 8;
}
else if ("LineNumberTable".equals(attrName)) {
readLineNumberTable(context, u);
}
u += 6 + readInt(u + 4);
}
readBytecodeInstructionsInCodeBlock(mv, context, codeStart, codeEnd);
Label label = labels[codeLength];
if (label != null) {
mv.visitLabel(label);
}
if (varTable != 0 && context.readDebugInfo()) {
readLocalVariableTables(mv, context, varTable, varTypeTable);
}
mv.visitMaxStack(maxStack);
}
private int readAllLabelsInCodeBlock(Context context, int u, int codeLength, int codeStart, int codeEnd) {
byte[] b = this.b;
Label[] labels = context.labels;
readLabel(codeLength + 1, labels);
while (u < codeEnd) {
int offset = u - codeStart;
int opcode = b[u] & 0xFF;
switch (TYPE[opcode]) {
case NOARG:
case IMPLVAR:
u++;
break;
case LABEL:
readLabel(offset + readShort(u + 1), labels);
u += 3;
break;
case LABELW:
readLabel(offset + readInt(u + 1), labels);
u += 5;
break;
case InstructionType.WIDE:
opcode = b[u + 1] & 0xFF;
u += opcode == IINC ? 6 : 4;
break;
case TABL:
u = readTableSwitchInstruction(labels, u, offset);
break;
case LOOK:
u = readLookupSwitchInstruction(labels, u, offset);
break;
case VAR:
case SBYTE:
case InstructionType.LDC:
u += 2;
break;
case SHORT:
case LDCW:
case FIELDORMETH:
case InstructionType.TYPE:
case IINC:
u += 3;
break;
case ITFMETH:
case INDYMETH:
u += 5;
break;
// case MANA_INSN:
default:
u += 4;
break;
}
}
return u;
}
// Reads the try catch entries to find the labels, and also visits them.
private int readTryCatchBlocks(MethodVisitor mv, Context context, int u) {
char[] c = context.buffer;
Label[] labels = context.labels;
for (int i = readUnsignedShort(u); i > 0; i--) {
Label start = readLabel(readUnsignedShort(u + 2), labels);
Label end = readLabel(readUnsignedShort(u + 4), labels);
Label handler = readLabel(readUnsignedShort(u + 6), labels);
String type = readUTF8(items[readUnsignedShort(u + 8)], c);
mv.visitTryCatchBlock(start, end, handler, type);
u += 8;
}
return u;
}
private int readLocalVariableTable(Context context, int u, int varTable) {
if (context.readDebugInfo()) {
Label[] labels = context.labels;
varTable = u + 8;
for (int j = readUnsignedShort(u + 8), v = u; j > 0; j--) {
int label = readUnsignedShort(v + 10);
if (labels[label] == null) {
readDebugLabel(label, labels);
}
label += readUnsignedShort(v + 12);
if (labels[label] == null) {
readDebugLabel(label, labels);
}
v += 10;
}
}
return varTable;
}
private void readLineNumberTable(Context context, int u) {
if (context.readDebugInfo()) {
Label[] labels = context.labels;
for (int j = readUnsignedShort(u + 8), v = u; j > 0; j--) {
int label = readUnsignedShort(v + 10);
if (labels[label] == null) {
readDebugLabel(label, labels);
}
labels[label].line = readUnsignedShort(v + 12);
v += 4;
}
}
}
private void readBytecodeInstructionsInCodeBlock(MethodVisitor mv, Context context, int codeStart, int codeEnd) {
Label[] labels = context.labels;
boolean readDebugInfo = context.readDebugInfo();
char[] c = context.buffer;
byte[] b = this.b;
int u = codeStart;
while (u < codeEnd) {
int offset = u - codeStart;
readLabelAndLineNumber(mv, labels[offset], readDebugInfo);
// Visits the instruction at this offset.
int opcode = b[u] & 0xFF;
switch (TYPE[opcode]) {
case NOARG:
mv.visitInsn(opcode);
u++;
break;
case IMPLVAR:
readImplicitVarInstruction(mv, opcode);
u++;
break;
case LABEL:
Label targetLabel = labels[offset + readShort(u + 1)];
mv.visitJumpInsn(opcode, targetLabel);
u += 3;
break;
case LABELW:
Label targetLabelW = labels[offset + readInt(u + 1)];
mv.visitJumpInsn(opcode - 33, targetLabelW);
u += 5;
break;
case InstructionType.WIDE:
u = readWideInstruction(mv, u);
break;
case TABL:
u = readTableSwitchInstruction(mv, labels, u, offset);
break;
case LOOK:
u = readLookupSwitchInstruction(mv, labels, u, offset);
break;
case VAR:
int var = b[u + 1] & 0xFF;
mv.visitVarInsn(opcode, var);
u += 2;
break;
case SBYTE:
byte byteOperand = b[u + 1];
mv.visitIntInsn(opcode, byteOperand);
u += 2;
break;
case SHORT:
int shortOperand = readShort(u + 1);
mv.visitIntInsn(opcode, shortOperand);
u += 3;
break;
case InstructionType.LDC:
Object cst = readConst(b[u + 1] & 0xFF, c);
mv.visitLdcInsn(cst);
u += 2;
break;
case LDCW:
Object cstWide = readConst(readUnsignedShort(u + 1), c);
mv.visitLdcInsn(cstWide);
u += 3;
break;
case FIELDORMETH:
case ITFMETH:
readFieldOrInvokeInstruction(mv, c, u, opcode);
u += opcode == INVOKEINTERFACE ? 5 : 3;
break;
case INDYMETH:
u = readInvokeDynamicInstruction(mv, context, u);
break;
case InstructionType.TYPE:
String typeDesc = readClass(u + 1, c);
mv.visitTypeInsn(opcode, typeDesc);
u += 3;
break;
case IINC:
int incCar = b[u + 1] & 0xFF;
byte increment = b[u + 2];
mv.visitIincInsn(incCar, increment);
u += 3;
break;
// case MANA_INSN:
default:
String arrayTypeDesc = readClass(u + 1, c);
int dims = b[u + 3] & 0xFF;
mv.visitMultiANewArrayInsn(arrayTypeDesc, dims);
u += 4;
break;
}
}
}
// Visits the label and line number for this offset, if any.
private void readLabelAndLineNumber(MethodVisitor mv, Label label, boolean readDebugInfo) {
if (label != null) {
mv.visitLabel(label);
if (readDebugInfo && label.line > 0) {
mv.visitLineNumber(label.line, label);
}
}
}
private void readImplicitVarInstruction(MethodVisitor mv, int opcode) {
int opcodeBase;
if (opcode > ISTORE) {
opcode -= ISTORE_0;
opcodeBase = ISTORE;
}
else {
opcode -= ILOAD_0;
opcodeBase = ILOAD;
}
mv.visitVarInsn(opcodeBase + (opcode >> 2), opcode & 0x3);
}
private int readWideInstruction(MethodVisitor mv, int u) {
int opcode = b[u + 1] & 0xFF;
int var = readUnsignedShort(u + 2);
if (opcode == IINC) {
int increment = readShort(u + 4);
mv.visitIincInsn(var, increment);
u += 6;
}
else {
mv.visitVarInsn(opcode, var);
u += 4;
}
return u;
}
private int readTableSwitchInstruction(MethodVisitor mv, Label[] labels, int u, int offset) {
// Skips 0 to 3 padding bytes.
u = u + 4 - (offset & 3);
// Reads instruction.
int dfltLabelIndex = offset + readInt(u);
int min = readInt(u + 4);
int max = readInt(u + 8);
Label[] table = new Label[max - min + 1];
u += 12;
for (int i = 0; i < table.length; i++) {
int handlerLabelIndex = offset + readInt(u);
table[i] = labels[handlerLabelIndex];
u += 4;
}
Label dfltLabel = labels[dfltLabelIndex];
mv.visitTableSwitchInsn(min, max, dfltLabel, table);
return u;
}
private int readLookupSwitchInstruction(MethodVisitor mv, Label[] labels, int u, int offset) {
// Skips 0 to 3 padding bytes.
u = u + 4 - (offset & 3);
// Reads the instruction.
int dfltLabelIndex = offset + readInt(u);
int len = readInt(u + 4);
int[] keys = new int[len];
Label[] values = new Label[len];
u += 8;
for (int i = 0; i < len; i++) {
keys[i] = readInt(u);
int handlerLabelIndex = offset + readInt(u + 4);
values[i] = labels[handlerLabelIndex];
u += 8;
}
Label dfltLabel = labels[dfltLabelIndex];
mv.visitLookupSwitchInsn(dfltLabel, keys, values);
return u;
}
private void readFieldOrInvokeInstruction(MethodVisitor mv, char[] c, int u, int opcode) {
int cpIndex1 = items[readUnsignedShort(u + 1)];
String owner = readClass(cpIndex1, c);
int cpIndex2 = items[readUnsignedShort(cpIndex1 + 2)];
String name = readUTF8(cpIndex2, c);
String desc = readUTF8(cpIndex2 + 2, c);
if (opcode < INVOKEVIRTUAL) {
mv.visitFieldInsn(opcode, owner, name, desc);
}
else {
boolean itf = b[cpIndex1 - 1] == ConstantPoolItemType.IMETH;
mv.visitMethodInsn(opcode, owner, name, desc, itf);
}
}
private void readLocalVariableTables(MethodVisitor mv, Context context, int varTable, int varTypeTable) {
char[] c = context.buffer;
Label[] labels = context.labels;
int[] typeTable = null;
int u;
if (varTypeTable != 0) {
u = varTypeTable + 2;
typeTable = new int[readUnsignedShort(varTypeTable) * 3];
for (int i = typeTable.length; i > 0; ) {
typeTable[--i] = u + 6; // signature
typeTable[--i] = readUnsignedShort(u + 8); // index
typeTable[--i] = readUnsignedShort(u); // start
u += 10;
}
}
u = varTable + 2;
for (int i = readUnsignedShort(varTable); i > 0; --i) {
int start = readUnsignedShort(u);
int length = readUnsignedShort(u + 2);
int index = readUnsignedShort(u + 8);
String signature = null;
if (typeTable != null) {
for (int j = 0; j < typeTable.length; j += 3) {
if (typeTable[j] == start && typeTable[j + 1] == index) {
signature = readUTF8(typeTable[j + 2], c);
break;
}
}
}
String name = readUTF8(u + 4, c);
String desc = readUTF8(u + 6, c);
mv.visitLocalVariable(name, desc, signature, labels[start], labels[start + length], index);
u += 10;
}
}
private int readInvokeDynamicInstruction(MethodVisitor mv, Context context, int u) {
int cpIndex = items[readUnsignedShort(u + 1)];
int bsmIndex = context.bootstrapMethods[readUnsignedShort(cpIndex)];
char[] c = context.buffer;
Handle bsm = (Handle) readConst(readUnsignedShort(bsmIndex), c);
int bsmArgCount = readUnsignedShort(bsmIndex + 2);
Object[] bsmArgs = new Object[bsmArgCount];
bsmIndex += 4;
for (int i = 0; i < bsmArgCount; i++) {
bsmArgs[i] = readConst(readUnsignedShort(bsmIndex), c);
bsmIndex += 2;
}
cpIndex = items[readUnsignedShort(cpIndex + 2)];
String name = readUTF8(cpIndex, c);
String desc = readUTF8(cpIndex + 2, c);
mv.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
u += 5;
return u;
}
private int readTableSwitchInstruction(Label[] labels, int u, int offset) {
// Skips 0 to 3 padding bytes.
u = u + 4 - (offset & 3);
// Reads instruction.
readLabel(offset + readInt(u), labels);
for (int i = readInt(u + 8) - readInt(u + 4) + 1; i > 0; --i) {
readLabel(offset + readInt(u + 12), labels);
u += 4;
}
return u + 12;
}
private int readLookupSwitchInstruction(Label[] labels, int u, int offset) {
// Skips 0 to 3 padding bytes.
u = u + 4 - (offset & 3);
// Reads instruction.
readLabel(offset + readInt(u), labels);
for (int i = readInt(u + 4); i > 0; --i) {
readLabel(offset + readInt(u + 12), labels);
u += 8;
}
return u + 8;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy