/*
* Copyright 2013 LMAX Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.freud.analysed.classbytecode.parser.asm;
import org.freud.analysed.classbytecode.ClassByteCodeInnerClass;
import org.freud.analysed.classbytecode.method.ClassByteCodeMethod;
import org.freud.analysed.classbytecode.method.LocalVariable;
import org.freud.analysed.classbytecode.method.instruction.AbstractOperandStack;
import org.freud.analysed.classbytecode.method.instruction.ConstInstruction;
import org.freud.analysed.classbytecode.method.instruction.FieldInstruction;
import org.freud.analysed.classbytecode.method.instruction.Instruction;
import org.freud.analysed.classbytecode.method.instruction.InstructionVisitor;
import org.freud.analysed.classbytecode.method.instruction.IntOperandInstruction;
import org.freud.analysed.classbytecode.method.instruction.JumpInstruction;
import org.freud.analysed.classbytecode.method.instruction.Label;
import org.freud.analysed.classbytecode.method.instruction.MethodInvocationInstruction;
import org.freud.analysed.classbytecode.method.instruction.Opcode;
import org.freud.analysed.classbytecode.method.instruction.OperandStack;
import org.freud.analysed.classbytecode.method.instruction.ReferenceOperandInstruction;
import org.freud.analysed.classbytecode.method.instruction.StaticOperandStack;
import org.freud.analysed.classbytecode.method.instruction.VarInstruction;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
final class AsmMethod extends AsmElement implements MethodVisitor, ClassByteCodeMethod {
private static final Pattern METHOD_DESC_PATTERN = Pattern.compile("\\((.*)\\)(.+)");
private static final Opcode[] OPCODES_ARRAY = Opcode.values();
private static final String[] NEWARRAY_TYPES =
{
null, null, null, null,
boolean.class.getCanonicalName(), char.class.getCanonicalName(),
float.class.getCanonicalName(), double.class.getCanonicalName(),
byte.class.getCanonicalName(), short.class.getCanonicalName(),
int.class.getCanonicalName(), long.class.getCanonicalName(),
};
private final String name;
private final String desc;
private final String signature;
private final String[] exceptions;
private final List<ClassByteCodeInnerClass> innerClassList;
private final List<Instruction> instructionList;
private final LinkedHashMap<String, LocalVariable> variableByNameMap;
private final Map<org.objectweb.asm.Label, Label> labelByAsmLabelMap;
private int currentLineNumber;
private OperandStack currentOperandStack = AbstractOperandStack.EMPTY_STACK;
private List<String> currentLocals;
private String returnType;
public AsmMethod(final AsmClassByteCode classByteCode, final int access, final String name, final String desc, final String signature, final String... exceptions) {
super(access);
this.signature = signature;
this.exceptions = exceptions;
this.name = name;
this.desc = desc;
this.innerClassList = new LinkedList<ClassByteCodeInnerClass>();
this.instructionList = new ArrayList<Instruction>();
this.labelByAsmLabelMap = new HashMap<org.objectweb.asm.Label, Label>();
this.variableByNameMap = new LinkedHashMap<String, LocalVariable>();
for (ClassByteCodeInnerClass innerClass : classByteCode.getInnerClassList()) {
if (innerClass.isAnonymous() && name.equals(innerClass.getOuterName()) && desc.equals(innerClass.getOuterDesc())) {
innerClassList.add(innerClass);
}
}
classByteCode.addMethod(this);
currentLocals = new ArrayList<String>();
initLocals(desc);
this.currentLineNumber = -1;
}
@Override
public void findInstruction(final InstructionVisitor instructionVisitor) {
for (final Instruction instruction : instructionList) {
instruction.visit(instructionVisitor);
}
}
@Override
public String getReturnType() {
return returnType;
}
@Override
public Instruction getInstruction(final int index) {
return instructionList.get(index);
}
@Override
public List<ClassByteCodeInnerClass> getAnonymousClassList() {
return innerClassList;
}
@Override
public LocalVariable getLocalVariable(final String name) {
return variableByNameMap.get(name);
}
@Override
public LocalVariable getLocalVariable(final int index) {
int i = 0;
for (LocalVariable variable : variableByNameMap.values()) {
if (index == i++) {
return variable;
}
}
return null;
}
@Override
public String getLocalVariableType(final int index) {
return currentLocals.get(index);
}
@Override
public boolean isStatic() {
return isAccessModifier(Opcodes.ACC_STATIC);
}
@Override
public boolean isSynchronized() {
return isAccessModifier(Opcodes.ACC_SYNCHRONIZED);
}
@Override
public boolean isBridge() {
return isAccessModifier(Opcodes.ACC_BRIDGE);
}
@Override
public boolean isVarargs() {
return isAccessModifier(Opcodes.ACC_VARARGS);
}
@Override
public boolean isNative() {
return isAccessModifier(Opcodes.ACC_NATIVE);
}
@Override
public boolean isStrict() {
return isAccessModifier(Opcodes.ACC_STRICT);
}
@Override
public boolean isAbstract() {
return isAccessModifier(Opcodes.ACC_ABSTRACT);
}
@Override
public String getName() {
return name;
}
@Override
public String getDesc() {
return desc;
}
@Override
public String getSignature() {
return signature;
}
@Override
public String[] getExceptions() {
return exceptions;
}
@Override
public AnnotationVisitor visitAnnotationDefault() {
return new AsmAnnotation(this);
}
@Override
public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) {
return new AsmAnnotation(this, desc, visible);
}
@Override
public AnnotationVisitor visitParameterAnnotation(final int parameter, final String desc, final boolean visible) {
return new AsmAnnotation(this, parameter, desc, visible);
}
@Override
public void visitAttribute(final Attribute attr) {
// no op
}
public void visitCode() {
// no op
}
public void visitInsn(final int opcode) {
final Instruction instruction = new Instruction(instructionList.size(), OPCODES_ARRAY[opcode], currentLineNumber);
updateCurrentState(instruction);
}
public void visitIntInsn(final int opcodeUsed, final int operand) {
final Opcode opcode = OPCODES_ARRAY[opcodeUsed];
final Instruction instruction;
if (opcode == Opcode.NEWARRAY) {
instruction = new ReferenceOperandInstruction(instructionList.size(), opcode, currentLineNumber, NEWARRAY_TYPES[operand]);
}
else {
instruction = new IntOperandInstruction(instructionList.size(), opcode, currentLineNumber, operand);
}
updateCurrentState(instruction);
}
public void visitVarInsn(final int opcodeUsed, final int var) {
final Instruction instruction = new VarInstruction(instructionList.size(), OPCODES_ARRAY[opcodeUsed], currentLineNumber, var);
updateCurrentState(instruction);
}
public void visitTypeInsn(final int opcodeUsed, final String type) {
final Opcode opcode = OPCODES_ARRAY[opcodeUsed];
final String operandType = "L" + type + ";";
final Instruction instruction = new ReferenceOperandInstruction(instructionList.size(), opcode, currentLineNumber, operandType);
updateCurrentState(instruction);
}
public void visitFieldInsn(
final int opcode,
final String owner,
final String name,
final String desc) {
final Instruction instruction = new FieldInstruction(instructionList.size(), OPCODES_ARRAY[opcode], currentLineNumber, owner, name, desc);
updateCurrentState(instruction);
}
public void visitMethodInsn(
final int opcode,
final String owner,
final String name,
final String desc) {
final Matcher matcher = METHOD_DESC_PATTERN.matcher(desc);
if (matcher.matches()) {
final String argsAsString = matcher.group(1);
final ArrayList<String> argsContainer = new ArrayList<String>();
String returnType = matcher.group(2);
parseArgs(argsAsString, argsContainer);
String[] args = argsContainer.toArray(new String[argsContainer.size()]);
final Instruction instruction = new MethodInvocationInstruction(instructionList.size(), OPCODES_ARRAY[opcode], currentLineNumber,
"L" + owner + ";", name, args, returnType);
updateCurrentState(instruction);
}
}
public void visitJumpInsn(final int opcode, final org.objectweb.asm.Label asmLabel) {
Label label = declareLabel(asmLabel);
// System.out.println(name + " " + OPCODES_ARRAY[opcode] + " " + asmLabel + " " + label);
final Instruction instruction = new JumpInstruction(instructionList.size(), OPCODES_ARRAY[opcode], currentLineNumber, label);
updateCurrentState(instruction);
}
public void visitLabel(final org.objectweb.asm.Label asmLabel) {
final Label label = declareLabel(asmLabel);
label.declare(instructionList.size());
final String handledType = label.getHandledType();
if (handledType != null) {
currentOperandStack = new StaticOperandStack(handledType, currentOperandStack, null);
}
}
public void visitLdcInsn(final Object constant) {
final Instruction instruction;
if (constant instanceof Type) {
instruction = new ReferenceOperandInstruction(instructionList.size(), Opcode.LDC, currentLineNumber, constant.toString());
}
else {
instruction = new ConstInstruction(instructionList.size(), Opcode.LDC, currentLineNumber, constant);
}
updateCurrentState(instruction);
}
public void visitIincInsn(final int var, final int increment) {
final Instruction instruction = new IntOperandInstruction(instructionList.size(), Opcode.IINC, currentLineNumber, increment);
updateCurrentState(instruction);
}
public void visitTableSwitchInsn(
final int min,
final int max,
final org.objectweb.asm.Label dflt,
final org.objectweb.asm.Label[] labels) {
for (int i = 0; i < labels.length; ++i) {
declareLookupLabel(labels[i], min + i);
}
declareDefaultLookupLabel(dflt);
}
public void visitLookupSwitchInsn(
final org.objectweb.asm.Label dflt,
final int[] keys,
final org.objectweb.asm.Label[] labels) {
for (int i = 0; i < labels.length; ++i) {
declareLookupLabel(labels[i], keys[i]);
}
declareDefaultLookupLabel(dflt);
}
public void visitTryCatchBlock(
final org.objectweb.asm.Label start,
final org.objectweb.asm.Label end,
final org.objectweb.asm.Label handler,
final String type) {
declareHandlerLabel(handler, (type != null) ? "L" + type + ";" : "Ljava/lang/Throwable;");
declareLabel(start);
declareLabel(end);
}
public void visitFrame(
final int type,
final int nLocal,
final Object[] local,
final int nStack,
final Object[] stack) {
final FrameType frameType = FrameType.getFrameType(type);
switch (frameType) {
case F_SAME:
currentOperandStack = AbstractOperandStack.EMPTY_STACK;
break;
case F_SAME1:
currentOperandStack = new StaticOperandStack(getTypeFromFrame(stack[0]), AbstractOperandStack.EMPTY_STACK, null);
break;
case F_APPEND:
currentOperandStack = AbstractOperandStack.EMPTY_STACK;
for (int i = 0; i < nLocal; i++) {
currentLocals.add(getTypeFromFrame(local[i]));
}
break;
case F_CHOP:
currentOperandStack = AbstractOperandStack.EMPTY_STACK;
currentLocals = currentLocals.subList(0, currentLocals.size() - nLocal);
break;
case F_FULL:
currentLocals = new ArrayList<String>();
for (int i = 0; i < nLocal; i++) {
currentLocals.add(getTypeFromFrame(local[i]));
}
currentOperandStack = AbstractOperandStack.EMPTY_STACK;
for (int i = 0; i < nStack; i++) {
currentOperandStack = new StaticOperandStack(getTypeFromFrame(stack[i]), currentOperandStack, null);
}
break;
case F_NEW:
break;
}
}
public void visitMultiANewArrayInsn(final String desc, final int dims) {
final Instruction instruction = new ReferenceOperandInstruction(instructionList.size(), Opcode.MULTIANEWARRAY, currentLineNumber, desc, dims);
updateCurrentState(instruction);
}
public void visitLocalVariable(
final String name,
final String desc,
final String signature,
final org.objectweb.asm.Label start,
final org.objectweb.asm.Label end,
final int index) {
variableByNameMap.put(name, new LocalVariable(name, desc, signature, declareLabel(start), declareLabel(end)));
}
public void visitLineNumber(final int line, final org.objectweb.asm.Label start) {
if (currentLineNumber < line) {
currentLineNumber = line;
}
labelByAsmLabelMap.get(start).setLineNumber(line);
}
public void visitMaxs(final int maxStack, final int maxLocals) {
// TODO
}
@Override
public void visitEnd() {
// no op
}
private String getTypeFromFrame(final Object item) {
if (item instanceof String) {
final String strItem = (String) item;
return (strItem.indexOf('/') > -1) ? "L" + strItem + ";" : strItem;
}
else if (item instanceof org.objectweb.asm.Label) {
return "Ljava/lang/Object;";
}
else if (item instanceof Integer) {
final FrameValueType valueType = FrameValueType.getFrameValueType((Integer) item);
switch (valueType) {
case TOP:
case NULL:
case UNINITIALIZED_THIS:
return "Ljava/lang/Object;";
case INTEGER:
return "I";
case FLOAT:
return "F";
case LONG:
return "J";
case DOUBLE:
return "D";
default:
throw new IllegalArgumentException("frame item " + item);
}
}
else {
throw new IllegalArgumentException("frame item " + item);
}
}
private void initLocals(final String desc) {
final Matcher matcher = METHOD_DESC_PATTERN.matcher(desc);
if (matcher.matches()) {
final String paramsAsString = matcher.group(1);
parseArgs(paramsAsString, currentLocals);
returnType = matcher.group(2);
}
else {
throw new IllegalArgumentException("desc " + desc);
}
}
@Override
public String toString() {
return "AsmMethod[" + name + "]";
}
////////////////////////////////////////////////////////////////////////
private void parseArgs(final String argsAsString, final List<String> args) {
boolean start = true;
final int len = argsAsString.length();
int ptr = 0;
for (int i = 0; i < len; i++) {
final char c = argsAsString.charAt(i);
switch (c) {
case '[':
case 'L':
start = false;
break;
case ';':
args.add(argsAsString.substring(ptr, i + 1));
ptr = i + 1;
start = true;
break;
case 'B':
case 'C':
case 'D':
case 'F':
case 'I':
case 'J':
case 'S':
case 'Z':
if (start) {
args.add(String.valueOf(c));
ptr = i + 1;
}
break;
}
}
if (ptr < len) {
args.add(argsAsString.substring(ptr));
}
}
private Label declareLabel(final org.objectweb.asm.Label asmLabel) {
return storeLabel(asmLabel, Label.create(instructionList.size()));
}
private Label declareHandlerLabel(final org.objectweb.asm.Label asmLabel, final String type) {
return storeLabel(asmLabel, Label.createHandler(instructionList.size(), type));
}
private Label declareLookupLabel(final org.objectweb.asm.Label asmLabel, final int key) {
return storeLabel(asmLabel, Label.createLookupKey(instructionList.size(), key));
}
private Label declareDefaultLookupLabel(final org.objectweb.asm.Label asmLabel) {
return storeLabel(asmLabel, Label.createDefaultLookupKey(instructionList.size()));
}
private Label storeLabel(final org.objectweb.asm.Label asmLabel, final Label label) {
final Label oldLabel = labelByAsmLabelMap.get(asmLabel);
if (oldLabel != null) {
return oldLabel;
}
else {
labelByAsmLabelMap.put(asmLabel, label);
return label;
}
}
private void updateCurrentState(final Instruction instruction) {
instructionList.add(instruction);
// System.out.println(name + " " + instruction + " " + labelByAsmLabelMap);
final Opcode opcode = instruction.getOpcode();
ensureCurrentLocalsSize(instruction.getVarIndex());
currentLocals = opcode.updateLocals(currentLocals, instruction);
currentOperandStack = opcode.updateOperandStack(this, instruction, currentOperandStack);
instruction.setOperandStack(currentOperandStack);
}
private void ensureCurrentLocalsSize(final int varIndex) {
while (varIndex >= currentLocals.size()) {
currentLocals.add("");
}
}
private enum FrameValueType {
TOP(Opcodes.TOP),
INTEGER(Opcodes.INTEGER),
FLOAT(Opcodes.FLOAT),
LONG(Opcodes.LONG),
DOUBLE(Opcodes.DOUBLE),
NULL(Opcodes.NULL),
UNINITIALIZED_THIS(Opcodes.UNINITIALIZED_THIS);
private final int asmValue;
FrameValueType(final int asmValue) {
this.asmValue = asmValue;
}
public static FrameValueType getFrameValueType(final int asmValue) {
for (FrameValueType valueType : FrameValueType.values()) {
if (valueType.asmValue == asmValue) {
return valueType;
}
}
throw new IllegalArgumentException("Unknown frame value type " + asmValue);
}
}
private enum FrameType {
F_NEW(Opcodes.F_NEW),
F_FULL(Opcodes.F_FULL),
F_APPEND(Opcodes.F_APPEND),
F_CHOP(Opcodes.F_CHOP),
F_SAME(Opcodes.F_SAME),
F_SAME1(Opcodes.F_SAME1);
private final int asmValue;
FrameType(final int asmValue) {
this.asmValue = asmValue;
}
public static FrameType getFrameType(final int asmValue) {
for (FrameType type : FrameType.values()) {
if (type.asmValue == asmValue) {
return type;
}
}
throw new IllegalArgumentException("Unknown frame type " + asmValue);
}
}
}