/**
* Copyright (c) 2012-2016 André Bargull
* Alle Rechte vorbehalten / All Rights Reserved. Use is subject to license terms.
*
* <https://github.com/anba/es6draft>
*/
package com.github.anba.es6draft.compiler.assembler;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.util.Printer;
import org.objectweb.asm.util.Textifier;
import org.objectweb.asm.util.TraceClassVisitor;
import org.objectweb.asm.util.TraceMethodVisitor;
/**
* Class encapsulating generated bytecode.
*/
public final class Code {
private static final int METHOD_LIMIT = 1 << 12;
private static final int JAVA_VERSION = Opcodes.V1_7;
private final ArrayList<ClassCode> classes = new ArrayList<>();
private final SourceInfo sourceInfo;
private final ClassCode mainClass;
private ExternConstantPool sharedConstantPool = null;
private ClassCode currentClass;
/**
* Constructs a new code object.
*
* @param access
* the access flags
* @param className
* the internal class name
* @param signature
* the class signature
* @param superClass
* the super-class
* @param interfaces
* the list of interfaces
* @param sourceInfo
* the source information object
*/
public Code(int access, String className, ClassSignature signature, Type superClass,
List<Type> interfaces, SourceInfo sourceInfo) {
this.sourceInfo = sourceInfo;
this.mainClass = newMainClass(this, access, className, signature, superClass, interfaces);
classes.add(mainClass);
setCurrentClass(mainClass);
}
private void setCurrentClass(ClassCode currentClass) {
this.currentClass = currentClass;
}
private ClassCode requestClassForMethod() {
if (currentClass.methodCount() >= METHOD_LIMIT) {
setCurrentClass(newClass(new InlineConstantPool(this)));
}
return currentClass;
}
private ClassCode newMainClass(Code code, int access, String className,
ClassSignature signature, Type superClass, List<Type> interfaces) {
ConstantPool constantPool = new InlineConstantPool(code);
return newClass(constantPool, access, className, signature, superClass, interfaces,
sourceInfo);
}
private ClassCode newClass(ConstantPool constantPool, int access, String className) {
return newClass(constantPool, access, className, ClassSignature.NONE, Types.Object,
Collections.<Type> emptyList(), sourceInfo);
}
private static ClassCode newClass(ConstantPool constantPool, int access, String className,
ClassSignature signature, Type superClass, List<Type> interfaces, SourceInfo sourceInfo) {
if ((access & ~Modifier.classModifiers()) != 0) {
throw new IllegalArgumentException();
}
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
cw.visit(JAVA_VERSION, access | Opcodes.ACC_SUPER, className, signature.toString(),
superClass.internalName(), toInternalNames(interfaces));
cw.visitSource(sourceInfo.getFileName(), sourceInfo.getSourceMap());
return new ClassCode(constantPool, className, cw);
}
private static String[] toInternalNames(List<Type> types) {
if (types.isEmpty()) {
return null;
}
int i = 0;
String[] names = new String[types.size()];
for (Type type : types) {
names[i++] = type.internalName();
}
return names;
}
/**
* Returns the list of generated {@link ClassCode} objects.
*
* @return the list of generated class code objects
*/
public List<ClassCode> getClasses() {
return classes;
}
/**
* Returns the shared constant pool instance.
*
* @return the shared constant pool
*/
ConstantPool getSharedConstantPool() {
if (sharedConstantPool == null) {
sharedConstantPool = new ExternConstantPool(this);
}
return sharedConstantPool;
}
/**
* Adds a new class.
*
* @param constantPool
* the constant pool instance to use
* @return the class code instance which represents the new class
*/
ClassCode newClass(ConstantPool constantPool) {
int access = Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL;
String className = mainClass.className + '~' + classes.size();
ClassCode classCode = newClass(constantPool, access, className);
classes.add(classCode);
return classCode;
}
/**
* Adds a new method to main class module.
*
* @param access
* the access flag
* @param methodDescriptor
* the method descriptor
* @return the method code instance which represents the new method
*/
public MethodCode newConstructor(int access, MethodTypeDescriptor methodDescriptor) {
return mainClass.newConstructor(access, methodDescriptor, null, null);
}
/**
* Adds a new method to a class module.
*
* @param access
* the access flag
* @param methodName
* the name of the new method
* @param methodDescriptor
* the method descriptor
* @return the method code instance which represents the new method
*/
public MethodCode newMethod(int access, String methodName, MethodTypeDescriptor methodDescriptor) {
return requestClassForMethod().newMethod(access, methodName, methodDescriptor, null, null);
}
/**
* Returns the decompiled byte code.
*
* @param bytes
* the class byte code
* @param simpleTypes
* {@code true} to use simple type representation
* @return the decompiled byte code
*/
public static String toByteCode(byte[] bytes, boolean simpleTypes) {
Printer p = simpleTypes ? new SimpleTypeTextifier() : new Textifier();
return toByteCode(new TraceClassVisitor(null, p, null), p, bytes);
}
/**
* Returns the decompiled method byte code.
*
* @param bytes
* the class byte code
* @param methodName
* the method name
* @return the decompiled byte code
*/
public static String methodToByteCode(byte[] bytes, String methodName) {
Printer p = new SimpleTypeTextifier();
return toByteCode(new FilterMethodVisitor(p, methodName), p, bytes);
}
private static String toByteCode(ClassVisitor cv, Printer p, byte[] bytes) {
new ClassReader(bytes).accept(cv, ClassReader.EXPAND_FRAMES);
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
p.print(pw);
pw.flush();
return sw.toString();
}
private static final class FilterMethodVisitor extends ClassVisitor {
private final Printer printer;
private final String methodName;
public FilterMethodVisitor(Printer p, String methodName) {
super(Opcodes.ASM5);
this.printer = p;
this.methodName = methodName;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature,
String[] exceptions) {
if (methodName.equals(name)) {
Printer p = printer.visitMethod(access, name, desc, signature, exceptions);
return new TraceMethodVisitor(null, p);
}
return null;
}
}
/**
* Class representing method code.
*/
public static final class MethodCode {
public final ClassCode classCode;
public final int access;
public final String methodName;
public final MethodTypeDescriptor methodDescriptor;
public final MethodVisitor methodVisitor;
MethodCode(ClassCode classCode, int access, String methodName,
MethodTypeDescriptor methodDescriptor, MethodVisitor methodVisitor) {
this.classCode = classCode;
this.access = access;
this.methodName = methodName;
this.methodDescriptor = methodDescriptor;
this.methodVisitor = methodVisitor;
}
}
/**
* Class representing class code.
*/
public static final class ClassCode {
private int methodCount = 0;
public final ConstantPool constantPool;
public final String className;
public final Type classType;
public final ClassWriter classWriter;
ClassCode(ConstantPool constantPool, String className, ClassWriter classWriter) {
this.constantPool = constantPool;
this.className = className;
this.classType = Type.forName(className);
this.classWriter = classWriter;
}
int methodCount() {
return methodCount;
}
public byte[] toByteArray() {
constantPool.close();
classWriter.visitEnd();
return classWriter.toByteArray();
}
public MethodCode newConstructor(int access, MethodTypeDescriptor methodDescriptor,
String signature, String[] exceptions) {
if ((access & ~Modifier.constructorModifiers()) != 0) {
throw new IllegalArgumentException();
}
methodCount += 1;
return new MethodCode(this, access, "<init>", methodDescriptor,
classWriter.visitMethod(access, "<init>", methodDescriptor.descriptor(),
signature, exceptions));
}
public MethodCode newMethod(int access, String methodName,
MethodTypeDescriptor methodDescriptor, String signature, String[] exceptions) {
if ((access & ~Modifier.methodModifiers()) != 0) {
throw new IllegalArgumentException();
}
methodCount += 1;
return new MethodCode(this, access, methodName, methodDescriptor,
classWriter.visitMethod(access, methodName, methodDescriptor.descriptor(),
signature, exceptions));
}
public void addField(int access, String fieldName, Type fieldDescriptor, String signature) {
if ((access & ~Modifier.fieldModifiers()) != 0) {
throw new IllegalArgumentException();
}
classWriter
.visitField(access, fieldName, fieldDescriptor.descriptor(), signature, null)
.visitEnd();
}
}
}