org.testifyproject.bytecode.ClassFileWriter Maven / Gradle / Ivy
/*
* Javassist, a Java-bytecode translator toolkit.
* Copyright (C) 1999- Shigeru Chiba. All Rights Reserved.
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in org.testifyproject.testifyprojectpliance with
* the License. Alternatively, the contents of this file may be used under
* the terms of the GNU Lesser General Public License Version 2.1 or later,
* or the Apache License Version 2.0.
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*/
package org.testifyproject.bytecode;
import java.io.OutputStream;
import java.io.DataOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
/**
* A quick class-file writer. This is useful when a generated
* class file is simple and the code generation should be fast.
*
* Example:
*
*
* ClassFileWriter cfw = new ClassFileWriter(ClassFile.JAVA_4, 0);
* ConstPoolWriter cpw = cfw.getConstPool();
*
* FieldWriter fw = cfw.getFieldWriter();
* fw.add(AccessFlag.PUBLIC, "value", "I", null);
* fw.add(AccessFlag.PUBLIC, "value2", "J", null);
*
* int thisClass = cpw.addClassInfo("sample/Test");
* int superClass = cpw.addClassInfo("java/lang/Object");
*
* MethodWriter mw = cfw.getMethodWriter();
*
* mw.begin(AccessFlag.PUBLIC, MethodInfo.nameInit, "()V", null, null);
* mw.add(Opcode.ALOAD_0);
* mw.add(Opcode.INVOKESPECIAL);
* int signature = cpw.addNameAndTypeInfo(MethodInfo.nameInit, "()V");
* mw.add16(cpw.addMethodrefInfo(superClass, signature));
* mw.add(Opcode.RETURN);
* mw.codeEnd(1, 1);
* mw.end(null, null);
*
* mw.begin(AccessFlag.PUBLIC, "one", "()I", null, null);
* mw.add(Opcode.ICONST_1);
* mw.add(Opcode.IRETURN);
* mw.codeEnd(1, 1);
* mw.end(null, null);
*
* byte[] classfile = cfw.end(AccessFlag.PUBLIC, thisClass, superClass,
* null, null);
*
*
* The code above generates the following class:
*
*
* package sample;
* public class Test {
* public int value;
* public long value2;
* public Test() { super(); }
* public one() { return 1; }
* }
*
*
* @since 3.13
*/
public class ClassFileWriter {
private ByteStream output;
private ConstPoolWriter constPool;
private FieldWriter fields;
private MethodWriter methods;
int thisClass, superClass;
/**
* Constructs a class file writer.
*
* @param major the major version ({@link ClassFile#JAVA_4}, {@link ClassFile#JAVA_5}, ...).
* @param minor the minor version (0 for JDK 1.3 and later).
*/
public ClassFileWriter(int major, int minor) {
output = new ByteStream(512);
output.writeInt(0xCAFEBABE); // magic
output.writeShort(minor);
output.writeShort(major);
constPool = new ConstPoolWriter(output);
fields = new FieldWriter(constPool);
methods = new MethodWriter(constPool);
}
/**
* Returns a constant pool.
*/
public ConstPoolWriter getConstPool() { return constPool; }
/**
* Returns a filed writer.
*/
public FieldWriter getFieldWriter() { return fields; }
/**
* Returns a method writer.
*/
public MethodWriter getMethodWriter() { return methods; }
/**
* Ends writing and returns the contents of the class file.
*
* @param accessFlags access flags.
* @param thisClass this class. an index indicating its CONSTANT_Class_info
.
* @param superClass super class. an index indicating its CONSTANT_Class_info
.
* @param interfaces implemented interfaces.
* index numbers indicating their ClassInfo
.
* It may be null.
* @param aw attributes of the class file. May be null.
*
* @see AccessFlag
*/
public byte[] end(int accessFlags, int thisClass, int superClass,
int[] interfaces, AttributeWriter aw) {
constPool.end();
output.writeShort(accessFlags);
output.writeShort(thisClass);
output.writeShort(superClass);
if (interfaces == null)
output.writeShort(0);
else {
int n = interfaces.length;
output.writeShort(n);
for (int i = 0; i < n; i++)
output.writeShort(interfaces[i]);
}
output.enlarge(fields.dataSize() + methods.dataSize() + 6);
try {
output.writeShort(fields.size());
fields.write(output);
output.writeShort(methods.numOfMethods());
methods.write(output);
}
catch (IOException e) {}
writeAttribute(output, aw, 0);
return output.toByteArray();
}
/**
* Ends writing and writes the contents of the class file into the
* given output stream.
*
* @param accessFlags access flags.
* @param thisClass this class. an index indicating its CONSTANT_Class_info
.
* @param superClass super class. an index indicating its CONSTANT_Class_info
.
* @param interfaces implemented interfaces.
* index numbers indicating their CONSTATNT_Class_info
.
* It may be null.
* @param aw attributes of the class file. May be null.
*
* @see AccessFlag
*/
public void end(DataOutputStream out,
int accessFlags, int thisClass, int superClass,
int[] interfaces, AttributeWriter aw)
throws IOException
{
constPool.end();
output.writeTo(out);
out.writeShort(accessFlags);
out.writeShort(thisClass);
out.writeShort(superClass);
if (interfaces == null)
out.writeShort(0);
else {
int n = interfaces.length;
out.writeShort(n);
for (int i = 0; i < n; i++)
out.writeShort(interfaces[i]);
}
out.writeShort(fields.size());
fields.write(out);
out.writeShort(methods.numOfMethods());
methods.write(out);
if (aw == null)
out.writeShort(0);
else {
out.writeShort(aw.size());
aw.write(out);
}
}
/**
* This writes attributes.
*
* For example, the following object writes a synthetic attribute:
*
*
* ConstPoolWriter cpw = ...;
* final int tag = cpw.addUtf8Info("Synthetic");
* AttributeWriter aw = new AttributeWriter() {
* public int size() {
* return 1;
* }
* public void write(DataOutputStream out) throws java.io.IOException {
* out.writeShort(tag);
* out.writeInt(0);
* }
* };
*
*/
public static interface AttributeWriter {
/**
* Returns the number of attributes that this writer will
* write.
*/
public int size();
/**
* Writes all the contents of the attributes. The binary representation
* of the contents is an array of attribute_info
.
*/
public void write(DataOutputStream out) throws IOException;
}
static void writeAttribute(ByteStream bs, AttributeWriter aw, int attrCount) {
if (aw == null) {
bs.writeShort(attrCount);
return;
}
bs.writeShort(aw.size() + attrCount);
DataOutputStream dos = new DataOutputStream(bs);
try {
aw.write(dos);
dos.flush();
}
catch (IOException e) {}
}
/**
* Field.
*/
public static final class FieldWriter {
protected ByteStream output;
protected ConstPoolWriter constPool;
private int fieldCount;
FieldWriter(ConstPoolWriter cp) {
output = new ByteStream(128);
constPool = cp;
fieldCount = 0;
}
/**
* Adds a new field.
*
* @param accessFlags access flags.
* @param name the field name.
* @param descriptor the field type.
* @param aw the attributes of the field. may be null.
* @see AccessFlag
*/
public void add(int accessFlags, String name, String descriptor, AttributeWriter aw) {
int nameIndex = constPool.addUtf8Info(name);
int descIndex = constPool.addUtf8Info(descriptor);
add(accessFlags, nameIndex, descIndex, aw);
}
/**
* Adds a new field.
*
* @param accessFlags access flags.
* @param name the field name. an index indicating its CONSTANT_Utf8_info
.
* @param descriptor the field type. an index indicating its CONSTANT_Utf8_info
.
* @param aw the attributes of the field. may be null.
* @see AccessFlag
*/
public void add(int accessFlags, int name, int descriptor, AttributeWriter aw) {
++fieldCount;
output.writeShort(accessFlags);
output.writeShort(name);
output.writeShort(descriptor);
writeAttribute(output, aw, 0);
}
int size() { return fieldCount; }
int dataSize() { return output.size(); }
/**
* Writes the added fields.
*/
void write(OutputStream out) throws IOException {
output.writeTo(out);
}
}
/**
* Method.
*/
public static final class MethodWriter {
protected ByteStream output;
protected ConstPoolWriter constPool;
private int methodCount;
protected int codeIndex;
protected int throwsIndex;
protected int stackIndex;
private int startPos;
private boolean isAbstract;
private int catchPos;
private int catchCount;
MethodWriter(ConstPoolWriter cp) {
output = new ByteStream(256);
constPool = cp;
methodCount = 0;
codeIndex = 0;
throwsIndex = 0;
stackIndex = 0;
}
/**
* Starts Adding a new method.
*
* @param accessFlags access flags.
* @param name the method name.
* @param descriptor the method signature.
* @param exceptions throws clause. It may be null.
* The class names must be the JVM-internal
* representations like java/lang/Exception
.
* @param aw attributes to the Method_info
.
*/
public void begin(int accessFlags, String name, String descriptor,
String[] exceptions, AttributeWriter aw) {
int nameIndex = constPool.addUtf8Info(name);
int descIndex = constPool.addUtf8Info(descriptor);
int[] intfs;
if (exceptions == null)
intfs = null;
else
intfs = constPool.addClassInfo(exceptions);
begin(accessFlags, nameIndex, descIndex, intfs, aw);
}
/**
* Starts adding a new method.
*
* @param accessFlags access flags.
* @param name the method name. an index indicating its CONSTANT_Utf8_info
.
* @param descriptor the field type. an index indicating its CONSTANT_Utf8_info
.
* @param exceptions throws clause. indexes indicating CONSTANT_Class_info
s.
* It may be null.
* @param aw attributes to the Method_info
.
*/
public void begin(int accessFlags, int name, int descriptor, int[] exceptions, AttributeWriter aw) {
++methodCount;
output.writeShort(accessFlags);
output.writeShort(name);
output.writeShort(descriptor);
isAbstract = (accessFlags & AccessFlag.ABSTRACT) != 0;
int attrCount = isAbstract ? 0 : 1;
if (exceptions != null)
++attrCount;
writeAttribute(output, aw, attrCount);
if (exceptions != null)
writeThrows(exceptions);
if (!isAbstract) {
if (codeIndex == 0)
codeIndex = constPool.addUtf8Info(CodeAttribute.tag);
startPos = output.getPos();
output.writeShort(codeIndex);
output.writeBlank(12); // attribute_length, maxStack, maxLocals, code_lenth
}
catchPos = -1;
catchCount = 0;
}
private void writeThrows(int[] exceptions) {
if (throwsIndex == 0)
throwsIndex = constPool.addUtf8Info(ExceptionsAttribute.tag);
output.writeShort(throwsIndex);
output.writeInt(exceptions.length * 2 + 2);
output.writeShort(exceptions.length);
for (int i = 0; i < exceptions.length; i++)
output.writeShort(exceptions[i]);
}
/**
* Appends an 8bit value of bytecode.
*
* @see Opcode
*/
public void add(int b) {
output.write(b);
}
/**
* Appends a 16bit value of bytecode.
*/
public void add16(int b) {
output.writeShort(b);
}
/**
* Appends a 32bit value of bytecode.
*/
public void add32(int b) {
output.writeInt(b);
}
/**
* Appends a invokevirtual, inovkespecial, or invokestatic bytecode.
*
* @see Opcode
*/
public void addInvoke(int opcode, String targetClass, String methodName,
String descriptor) {
int target = constPool.addClassInfo(targetClass);
int nt = constPool.addNameAndTypeInfo(methodName, descriptor);
int method = constPool.addMethodrefInfo(target, nt);
add(opcode);
add16(method);
}
/**
* Ends appending bytecode.
*/
public void codeEnd(int maxStack, int maxLocals) {
if (!isAbstract) {
output.writeShort(startPos + 6, maxStack);
output.writeShort(startPos + 8, maxLocals);
output.writeInt(startPos + 10, output.getPos() - startPos - 14); // code_length
catchPos = output.getPos();
catchCount = 0;
output.writeShort(0); // number of catch clauses
}
}
/**
* Appends an exception_table
entry to the
* Code_attribute
. This method is available
* only after the codeEnd
method is called.
*
* @param catchType an index indicating a CONSTANT_Class_info
.
*/
public void addCatch(int startPc, int endPc, int handlerPc, int catchType) {
++catchCount;
output.writeShort(startPc);
output.writeShort(endPc);
output.writeShort(handlerPc);
output.writeShort(catchType);
}
/**
* Ends adding a new method. The add
method must be
* called before the end
method is called.
*
* @param smap a stack map table. may be null.
* @param aw attributes to the Code_attribute
.
* may be null.
*/
public void end(StackMapTable.Writer smap, AttributeWriter aw) {
if (isAbstract)
return;
// exception_table_length
output.writeShort(catchPos, catchCount);
int attrCount = smap == null ? 0 : 1;
writeAttribute(output, aw, attrCount);
if (smap != null) {
if (stackIndex == 0)
stackIndex = constPool.addUtf8Info(StackMapTable.tag);
output.writeShort(stackIndex);
byte[] data = smap.toByteArray();
output.writeInt(data.length);
output.write(data);
}
// Code attribute_length
output.writeInt(startPos + 2, output.getPos() - startPos - 6);
}
/**
* Returns the length of the bytecode that has been added so far.
*
* @return the length in bytes.
* @since 3.19
*/
public int size() { return output.getPos() - startPos - 14; }
int numOfMethods() { return methodCount; }
int dataSize() { return output.size(); }
/**
* Writes the added methods.
*/
void write(OutputStream out) throws IOException {
output.writeTo(out);
}
}
/**
* Constant Pool.
*/
public static final class ConstPoolWriter {
ByteStream output;
protected int startPos;
protected int num;
ConstPoolWriter(ByteStream out) {
output = out;
startPos = out.getPos();
num = 1;
output.writeShort(1); // number of entries
}
/**
* Makes CONSTANT_Class_info
objects for each class name.
*
* @return an array of indexes indicating CONSTANT_Class_info
s.
*/
public int[] addClassInfo(String[] classNames) {
int n = classNames.length;
int[] result = new int[n];
for (int i = 0; i < n; i++)
result[i] = addClassInfo(classNames[i]);
return result;
}
/**
* Adds a new CONSTANT_Class_info
structure.
*
* This also adds a CONSTANT_Utf8_info
structure
* for storing the class name.
*
* @param jvmname the JVM-internal representation of a class name.
* e.g. java/lang/Object
.
* @return the index of the added entry.
*/
public int addClassInfo(String jvmname) {
int utf8 = addUtf8Info(jvmname);
output.write(ClassInfo.tag);
output.writeShort(utf8);
return num++;
}
/**
* Adds a new CONSTANT_Class_info
structure.
*
* @param name name_index
* @return the index of the added entry.
*/
public int addClassInfo(int name) {
output.write(ClassInfo.tag);
output.writeShort(name);
return num++;
}
/**
* Adds a new CONSTANT_NameAndType_info
structure.
*
* @param name name_index
* @param type descriptor_index
* @return the index of the added entry.
*/
public int addNameAndTypeInfo(String name, String type) {
return addNameAndTypeInfo(addUtf8Info(name), addUtf8Info(type));
}
/**
* Adds a new CONSTANT_NameAndType_info
structure.
*
* @param name name_index
* @param type descriptor_index
* @return the index of the added entry.
*/
public int addNameAndTypeInfo(int name, int type) {
output.write(NameAndTypeInfo.tag);
output.writeShort(name);
output.writeShort(type);
return num++;
}
/**
* Adds a new CONSTANT_Fieldref_info
structure.
*
* @param classInfo class_index
* @param nameAndTypeInfo name_and_type_index
.
* @return the index of the added entry.
*/
public int addFieldrefInfo(int classInfo, int nameAndTypeInfo) {
output.write(FieldrefInfo.tag);
output.writeShort(classInfo);
output.writeShort(nameAndTypeInfo);
return num++;
}
/**
* Adds a new CONSTANT_Methodref_info
structure.
*
* @param classInfo class_index
* @param nameAndTypeInfo name_and_type_index
.
* @return the index of the added entry.
*/
public int addMethodrefInfo(int classInfo, int nameAndTypeInfo) {
output.write(MethodrefInfo.tag);
output.writeShort(classInfo);
output.writeShort(nameAndTypeInfo);
return num++;
}
/**
* Adds a new CONSTANT_InterfaceMethodref_info
* structure.
*
* @param classInfo class_index
* @param nameAndTypeInfo name_and_type_index
.
* @return the index of the added entry.
*/
public int addInterfaceMethodrefInfo(int classInfo,
int nameAndTypeInfo) {
output.write(InterfaceMethodrefInfo.tag);
output.writeShort(classInfo);
output.writeShort(nameAndTypeInfo);
return num++;
}
/**
* Adds a new CONSTANT_MethodHandle_info
* structure.
*
* @param kind reference_kind
* such as {@link ConstPool#REF_invokeStatic REF_invokeStatic
}.
* @param index reference_index
.
* @return the index of the added entry.
*
* @since 3.17.1
*/
public int addMethodHandleInfo(int kind, int index) {
output.write(MethodHandleInfo.tag);
output.write(kind);
output.writeShort(index);
return num++;
}
/**
* Adds a new CONSTANT_MethodType_info
* structure.
*
* @param desc descriptor_index
.
* @return the index of the added entry.
*
* @since 3.17.1
*/
public int addMethodTypeInfo(int desc) {
output.write(MethodTypeInfo.tag);
output.writeShort(desc);
return num++;
}
/**
* Adds a new CONSTANT_InvokeDynamic_info
* structure.
*
* @param bootstrap bootstrap_method_attr_index
.
* @param nameAndTypeInfo name_and_type_index
.
* @return the index of the added entry.
*
* @since 3.17.1
*/
public int addInvokeDynamicInfo(int bootstrap,
int nameAndTypeInfo) {
output.write(InvokeDynamicInfo.tag);
output.writeShort(bootstrap);
output.writeShort(nameAndTypeInfo);
return num++;
}
/**
* Adds a new CONSTANT_String_info
* structure.
*
*
This also adds a new CONSTANT_Utf8_info
* structure.
*
* @return the index of the added entry.
*/
public int addStringInfo(String str) {
int utf8 = addUtf8Info(str);
output.write(StringInfo.tag);
output.writeShort(utf8);
return num++;
}
/**
* Adds a new CONSTANT_Integer_info
* structure.
*
* @return the index of the added entry.
*/
public int addIntegerInfo(int i) {
output.write(IntegerInfo.tag);
output.writeInt(i);
return num++;
}
/**
* Adds a new CONSTANT_Float_info
* structure.
*
* @return the index of the added entry.
*/
public int addFloatInfo(float f) {
output.write(FloatInfo.tag);
output.writeFloat(f);
return num++;
}
/**
* Adds a new CONSTANT_Long_info
* structure.
*
* @return the index of the added entry.
*/
public int addLongInfo(long l) {
output.write(LongInfo.tag);
output.writeLong(l);
int n = num;
num += 2;
return n;
}
/**
* Adds a new CONSTANT_Double_info
* structure.
*
* @return the index of the added entry.
*/
public int addDoubleInfo(double d) {
output.write(DoubleInfo.tag);
output.writeDouble(d);
int n = num;
num += 2;
return n;
}
/**
* Adds a new CONSTANT_Utf8_info
* structure.
*
* @return the index of the added entry.
*/
public int addUtf8Info(String utf8) {
output.write(Utf8Info.tag);
output.writeUTF(utf8);
return num++;
}
/**
* Writes the contents of this class pool.
*/
void end() {
output.writeShort(startPos, num);
}
}
}