WALA/com.ibm.wala.shrike/src/com/ibm/wala/shrikeCT/ClassWriter.java

1081 lines
32 KiB
Java

/*******************************************************************************
* Copyright (c) 2002,2006 IBM Corporation.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package com.ibm.wala.shrikeCT;
import java.util.ArrayList;
import java.util.HashMap;
import com.ibm.wala.shrikeCT.BootstrapMethodsReader.BootstrapMethod;
import com.ibm.wala.shrikeCT.ConstantPoolParser.ReferenceToken;
/**
* This class formats and writes class data into JVM format.
*/
public class ClassWriter implements ClassConstants {
// input
private int majorVersion = 46;
private int minorVersion = 0;
private ConstantPoolParser rawCP;
private HashMap<Object, Integer> cachedCPEntries = new HashMap<>(1);
final private ArrayList<Object> newCPEntries = new ArrayList<>(1);
private int nextCPIndex = 1;
final private ArrayList<Element> fields = new ArrayList<>(1);
final private ArrayList<Element> methods = new ArrayList<>(1);
final private ArrayList<Element> classAttributes = new ArrayList<>(1);
private int thisClass;
private int superClass;
private int[] superInterfaces;
private int accessFlags;
private boolean forceAddCPEntries = false;
// output
private byte[] buf;
private int bufLen;
/**
* Create a blank ClassWriter with no methods, fields, or attributes, an empty constant pool, no super class, no implemented
* interfaces, no name, majorVersion 46, and minorVersion 0.
*/
public ClassWriter() {
}
/**
* Set the class file format major version. You probably don't want to use this unless you really know what you are doing.
*/
public void setMajorVersion(int major) {
if (major < 0 || major > 0xFFFF) {
throw new IllegalArgumentException("Major version out of range: " + major);
}
majorVersion = major;
}
/**
* Set the class file format minor version. You probably don't want to use this unless you really know what you are doing.
*/
public void setMinorVersion(int minor) {
if (minor < 0 || minor > 0xFFFF) {
throw new IllegalArgumentException("Major version out of range: " + minor);
}
minorVersion = minor;
}
static abstract class CWItem {
abstract byte getType();
}
public static class CWStringItem extends CWItem {
final private String s;
final private byte type;
public CWStringItem(String s, byte type) {
this.s = s;
this.type = type;
}
@Override
public boolean equals(Object o) {
return o != null && o.getClass().equals(getClass()) &&
((CWStringItem) o).type == type &&
((CWStringItem) o).s.equals(s);
}
@Override
public int hashCode() {
return s.hashCode() + (3901 * type) ;
}
@Override
byte getType() {
return type;
}
}
static class CWRef extends CWItem {
final protected String c;
final protected String n;
final protected String t;
final private byte type;
CWRef(byte type, String c, String n, String t) {
this.type = type;
this.c = c;
this.n = n;
this.t = t;
}
@Override
public boolean equals(Object o) {
if (o.getClass().equals(getClass())) {
CWRef r = (CWRef) o;
return r.type == type && r.c.equals(c) && r.n.equals(n) && r.t.equals(t);
} else {
return false;
}
}
@Override
public int hashCode() {
return type + (c.hashCode() << 5) + (n.hashCode() << 3) + t.hashCode();
}
@Override
byte getType() {
return type;
}
}
static class CWHandle extends CWRef {
private final byte kind;
CWHandle(byte type, byte kind, String c, String n, String t) {
super(type, c, n, t);
this.kind = kind;
}
@Override
public int hashCode() {
return super.hashCode() * kind;
}
@Override
public boolean equals(Object o) {
return super.equals(o) && ((CWHandle)o).kind == kind;
}
public byte getKind() {
return kind;
}
}
static class CWNAT extends CWItem {
final private String n;
final private String t;
CWNAT(String n, String t) {
this.n = n;
this.t = t;
}
@Override
public boolean equals(Object o) {
if (o instanceof CWNAT) {
CWNAT r = (CWNAT) o;
return r.n.equals(n) && r.t.equals(t);
} else {
return false;
}
}
@Override
public int hashCode() {
return (n.hashCode() << 3) + t.hashCode();
}
@Override
byte getType() {
return CONSTANT_NameAndType;
}
}
static class CWInvokeDynamic extends CWItem {
final private BootstrapMethod b;
final private String n;
final private String t;
CWInvokeDynamic(BootstrapMethod b, String n, String t) {
this.b = b;
this.n = n;
this.t = t;
}
@Override
public boolean equals(Object o) {
if (o instanceof CWInvokeDynamic) {
CWInvokeDynamic r = (CWInvokeDynamic) o;
return r.b.equals(b) && r.n.equals(n) && r.t.equals(t);
} else {
return false;
}
}
@Override
public int hashCode() {
return (b.hashCode() << 10) + (n.hashCode() << 3) + t.hashCode();
}
@Override
byte getType() {
return CONSTANT_InvokeDynamic;
}
}
/**
* Copy a constant pool from some ClassReader into this class. This must be done before any entries are allocated in this
* ClassWriter's constant pool, and it can only be done once. If and only if this is done, it is safe to copy "raw" fields,
* methods and attributes from the ClassReader into this class, because the constant pool references in those fields, methods and
* attributes are guaranteed to point to the same constant pool items in this new class.
*
* @param cacheEntries records whether to parse the raw constant pool completely so that if new entries are required which are the
* same as entries already in the raw pool, the existing entries in the raw pool are used instead. Setting this to 'true'
* produces smaller constant pools but may slow down performance because the raw pool must be completely parsed
*/
public void setRawCP(ConstantPoolParser cp, boolean cacheEntries) throws InvalidClassFileException, IllegalArgumentException {
if (cp == null) {
throw new IllegalArgumentException();
}
if (rawCP != null) {
throw new IllegalArgumentException("Cannot set raw constant pool twice");
}
if (nextCPIndex != 1) {
throw new IllegalArgumentException("Cannot set raw constant pool after allocating new entries");
}
rawCP = cp;
nextCPIndex = cp.getItemCount();
if (cacheEntries) {
for (int i = 1; i < nextCPIndex; i++) {
byte t = cp.getItemType(i);
switch (t) {
case CONSTANT_String:
cachedCPEntries.put(new CWStringItem(cp.getCPString(i), CONSTANT_String), Integer.valueOf(i));
break;
case CONSTANT_Class:
cachedCPEntries.put(new CWStringItem(cp.getCPClass(i), CONSTANT_Class), Integer.valueOf(i));
break;
case CONSTANT_MethodType:
cachedCPEntries.put(new CWStringItem(cp.getCPMethodType(i), CONSTANT_MethodType), Integer.valueOf(i));
break;
case CONSTANT_MethodHandle:
case CONSTANT_FieldRef:
case CONSTANT_InterfaceMethodRef:
case CONSTANT_MethodRef:
cachedCPEntries.put(new CWRef(t, cp.getCPRefClass(i), cp.getCPRefName(i), cp.getCPRefType(i)), Integer.valueOf(i));
break;
case CONSTANT_NameAndType:
cachedCPEntries.put(new CWNAT(cp.getCPNATName(i), cp.getCPNATType(i)), Integer.valueOf(i));
break;
case CONSTANT_InvokeDynamic:
cachedCPEntries.put(new CWInvokeDynamic(cp.getCPDynBootstrap(i), cp.getCPDynName(i), cp.getCPDynType(i)), Integer.valueOf(i));
break;
case CONSTANT_Integer:
cachedCPEntries.put(Integer.valueOf(cp.getCPInt(i)), Integer.valueOf(i));
break;
case CONSTANT_Float:
cachedCPEntries.put(Float.valueOf(cp.getCPFloat(i)), Integer.valueOf(i));
break;
case CONSTANT_Long:
cachedCPEntries.put(Long.valueOf(cp.getCPLong(i)), Integer.valueOf(i));
break;
case CONSTANT_Double:
cachedCPEntries.put(Double.valueOf(cp.getCPDouble(i)), Integer.valueOf(i));
break;
case CONSTANT_Utf8:
cachedCPEntries.put(cp.getCPUtf8(i), Integer.valueOf(i));
break;
default:
throw new UnsupportedOperationException(String.format("unexpected constant-pool item type %s", t));
}
}
}
}
/**
* @param force true iff you want the addCP methods to always create a new constant pool entry and never reuse an existing
* constant pool entry
*/
public void setForceAddCPEntries(boolean force) {
forceAddCPEntries = force;
}
protected int addCPEntry(Object o, int size) {
if (cachedCPEntries == null) {
throw new IllegalArgumentException("Cannot add a new constant pool entry during makeBytes() processing!");
}
Integer i = forceAddCPEntries ? null : cachedCPEntries.get(o);
if (i != null) {
return i.intValue();
} else {
int index = nextCPIndex;
nextCPIndex += size;
i = Integer.valueOf(index);
cachedCPEntries.put(o, i);
newCPEntries.add(o);
if (nextCPIndex > 0xFFFF) {
throw new IllegalArgumentException("Constant pool item count exceeded");
}
return index;
}
}
/**
* Add a Utf8 string to the constant pool if necessary.
*
* @return the index of a constant pool item with the right value
*/
public int addCPUtf8(String s) {
return addCPEntry(s, 1);
}
/**
* Add an Integer to the constant pool if necessary.
*
* @return the index of a constant pool item with the right value
*/
public int addCPInt(int i) {
return addCPEntry(Integer.valueOf(i), 1);
}
/**
* Add a Float to the constant pool if necessary.
*
* @return the index of a constant pool item with the right value
*/
public int addCPFloat(float f) {
return addCPEntry(Float.valueOf(f), 1);
}
/**
* Add a Long to the constant pool if necessary.
*
* @return the index of a constant pool item with the right value
*/
public int addCPLong(long l) {
return addCPEntry(Long.valueOf(l), 2);
}
/**
* Add a Double to the constant pool if necessary.
*
* @return the index of a constant pool item with the right value
*/
public int addCPDouble(double d) {
return addCPEntry(Double.valueOf(d), 2);
}
private int addCPString(String s, byte type) {
if (s == null) {
throw new IllegalArgumentException("null s: " + s);
}
return addCPEntry(new CWStringItem(s, type), 1);
}
public int addCPMethodHandle(ReferenceToken c) {
if (c == null) {
throw new IllegalArgumentException("null c: " + c);
}
return addCPEntry(new CWHandle(CONSTANT_MethodHandle, c.getKind(), c.getClassName(), c.getElementName(), c.getDescriptor()), 1);
}
/**
* Add a String to the constant pool if necessary.
*
* @return the index of a constant pool item with the right value
*/
public int addCPString(String s) {
return addCPString(s, CONSTANT_String);
}
/**
* Add a Class to the constant pool if necessary.
*
* @param s the class name, in JVM format (e.g., java/lang/Object)
* @return the index of a constant pool item with the right value
*/
public int addCPClass(String s) {
return addCPString(s, CONSTANT_Class);
}
/**
* Add a Class to the constant pool if necessary.
*
* @param s the class name, in JVM format (e.g., java/lang/Object)
* @return the index of a constant pool item with the right value
*/
public int addCPMethodType(String s) {
return addCPString(s, CONSTANT_MethodType);
}
/**
* Add a FieldRef to the constant pool if necessary.
*
* @param c the class name, in JVM format (e.g., java/lang/Object)
* @param n the field name
* @param t the field type, in JVM format (e.g., I, Z, or Ljava/lang/Object;)
* @return the index of a constant pool item with the right value
*/
public int addCPFieldRef(String c, String n, String t) {
return addCPEntry(new CWRef(CONSTANT_FieldRef, c, n, t), 1);
}
/**
* Add a MethodRef to the constant pool if necessary.
*
* @param c the class name, in JVM format (e.g., java/lang/Object)
* @param n the method name
* @param t the method type, in JVM format (e.g., V(ILjava/lang/Object;) )
* @return the index of a constant pool item with the right value
*/
public int addCPMethodRef(String c, String n, String t) {
return addCPEntry(new CWRef(CONSTANT_MethodRef, c, n, t), 1);
}
/**
* Add an InterfaceMethodRef to the constant pool if necessary.
*
* @param c the class name, in JVM format (e.g., java/lang/Object)
* @param n the field name
* @param t the method type, in JVM format (e.g., V(ILjava/lang/Object;) )
* @return the index of a constant pool item with the right value
*/
public int addCPInterfaceMethodRef(String c, String n, String t) {
return addCPEntry(new CWRef(CONSTANT_InterfaceMethodRef, c, n, t), 1);
}
/**
* Add a NameAndType to the constant pool if necessary.
*
* @param n the name
* @param t the type, in JVM format
* @return the index of a constant pool item with the right value
*/
public int addCPNAT(String n, String t) {
return addCPEntry(new CWNAT(n, t), 1);
}
/**
* Add an InvokeDynamic to the constant pool if necessary.
*
* @param n the name
* @param t the type, in JVM format
* @return the index of a constant pool item with the right value
*/
public int addCPInvokeDynamic(BootstrapMethod b, String n, String t) {
return addCPEntry(new CWInvokeDynamic(b, n, t), 1);
}
/**
* Set the access flags for the class.
*/
public void setAccessFlags(int f) {
if (f < 0 || f > 0xFFFF) {
throw new IllegalArgumentException("Access flags out of range: " + f);
}
accessFlags = f;
}
/**
* Set the constant pool index for the name of the class.
*/
public void setNameIndex(int c) throws IllegalArgumentException {
if (c < 1 || c > 0xFFFF) {
throw new IllegalArgumentException("Class name index out of range: " + c);
}
thisClass = c;
}
/**
* Set the constant pool index for the name of the superclass.
*/
public void setSuperNameIndex(int c) {
if (c < 0 || c > 0xFFFF) {
throw new IllegalArgumentException("Superclass name index out of range: " + c);
}
superClass = c;
}
/**
* Set the constant pool indices for the names of the implemented interfaces.
*/
public void setInterfaceNameIndices(int[] ifaces) {
if (ifaces != null) {
if (ifaces.length > 0xFFFF) {
throw new IllegalArgumentException("Too many interfaces implemented: " + ifaces.length);
}
for (int c : ifaces) {
if (c < 1 || c > 0xFFFF) {
throw new IllegalArgumentException("Interface name index out of range: " + c);
}
}
}
superInterfaces = ifaces;
}
/**
* Set the name of the class.
*/
public void setName(String c) {
setNameIndex(addCPClass(c));
}
/**
* Set the name of the superclass; if c is null, then there is no superclass (this must be java/lang/Object).
*/
public void setSuperName(String c) {
setSuperNameIndex(c == null ? 0 : addCPClass(c));
}
/**
* Set the names of the implemented interfaces.
*/
public void setInterfaceNames(String[] ifaces) {
if (ifaces == null) {
setInterfaceNameIndices((int[]) null);
} else {
int[] ifs = new int[ifaces.length];
for (int i = 0; i < ifaces.length; i++) {
ifs[i] = addCPClass(ifaces[i]);
}
setInterfaceNameIndices(ifs);
}
}
/**
* An Element is an object that can be serialized into a byte buffer. Serialization via 'copyInto' is performed when the user
* calls makeBytes() on the ClassWriter. At this time no new constant pool items can be allocated, so any item indices that need
* to be emitted must be allocated earlier.
*/
public static abstract class Element {
public Element() {
}
/**
* @return the number of bytes that will be generated.
*/
public abstract int getSize();
/**
* Copy the bytes into 'buf' at offset 'offset'.
*
* @return the number of bytes copies, which must be equal to getSize()
*/
public abstract int copyInto(byte[] buf, int offset);
}
/**
* A RawElement is an Element that is already available as some chunk of a byte buffer.
*/
public static final class RawElement extends Element {
final private byte[] buf;
final private int offset;
final private int len;
/**
* Create an Element for the 'len' bytes in 'buf' at offset 'offset'.
*/
public RawElement(byte[] buf, int offset, int len) {
this.buf = buf;
this.offset = offset;
this.len = len;
}
@Override
public int getSize() {
return len;
}
@Override
public int copyInto(byte[] dest, int destOffset) {
System.arraycopy(buf, offset, dest, destOffset, len);
return destOffset + len;
}
}
/**
* Add a method to the class, the method data given as "raw" bytes (probably obtained from a ClassReader).
*/
public void addRawMethod(Element e) {
methods.add(e);
}
/**
* Add a field to the class, the field data given as "raw" bytes (probably obtained from a ClassReader).
*/
public void addRawField(Element e) {
fields.add(e);
}
/**
* Add a method to the class.
*
* @param access the access flags
* @param name the method name
* @param type the method type in JVM format (e.g., V(ILjava/lang/Object;) )
* @param attributes the attributes in raw form, one Element per attribute
*/
public void addMethod(int access, String name, String type, Element[] attributes) {
addMethod(access, addCPUtf8(name), addCPUtf8(type), attributes);
}
/**
* Add a field to the class.
*
* @param access the access flags
* @param name the field name
* @param type the field type in JVM format (e.g., I, Z, Ljava/lang/Object;)
* @param attributes the attributes in raw form, one Element per attribute
*/
public void addField(int access, String name, String type, Element[] attributes) {
addField(access, addCPUtf8(name), addCPUtf8(type), attributes);
}
static final class MemberElement extends Element {
final private int access;
final private int name;
final private int type;
final private Element[] attributes;
public MemberElement(int access, int name, int type, Element[] attributes) {
if (access < 0 || access > 0xFFFF) {
throw new IllegalArgumentException("Access flags out of range: " + access);
}
if (name < 1 || name > 0xFFFF) {
throw new IllegalArgumentException("Name constant pool index out of range: " + name);
}
if (type < 1 || type > 0xFFFF) {
throw new IllegalArgumentException("Type constant pool index out of range: " + name);
}
if (attributes == null) {
throw new IllegalArgumentException("Atrtributes are null");
}
if (attributes.length > 0xFFFF) {
throw new IllegalArgumentException("Too many attributes: " + attributes.length);
}
this.access = access;
this.name = name;
this.type = type;
this.attributes = attributes;
}
@Override
public int getSize() {
int size = 8;
if (attributes != null) {
for (Element attribute : attributes) {
size += attribute.getSize();
}
}
return size;
}
@Override
public int copyInto(byte[] buf, int offset) {
setUShort(buf, offset, access);
setUShort(buf, offset + 2, name);
setUShort(buf, offset + 4, type);
if (attributes != null) {
setUShort(buf, offset + 6, attributes.length);
offset += 8;
for (Element attribute : attributes) {
offset = attribute.copyInto(buf, offset);
}
} else {
setUShort(buf, offset + 6, 0);
offset += 8;
}
return offset;
}
}
/**
* Add a method to the class.
*
* @param access the access flags
* @param name the constant pool index of the method name
* @param type the constant pool index of the method type in JVM format (e.g., V(ILjava/lang/Object;) )
* @param attributes the attributes in raw form, one Element per attribute
*/
public void addMethod(int access, int name, int type, Element[] attributes) {
// int idx=methods.size()-2;
// if (idx<0) idx=0;
// methods.add(0,new MemberElement(access, name, type, attributes));
methods.add(new MemberElement(access, name, type, attributes));
if (methods.size() > 0xFFFF) {
throw new IllegalArgumentException("Too many methods");
}
}
/**
* Add a field to the class.
*
* @param access the access flags
* @param name the constant pool index of the field name
* @param type the constant pool index of the field type in JVM format (e.g., I, Z, Ljava/lang/Object;)
* @param attributes the attributes in raw form, one Element per attribute
*/
public void addField(int access, int name, int type, Element[] attributes) {
fields.add(new MemberElement(access, name, type, attributes));
if (fields.size() > 0xFFFF) {
throw new IllegalArgumentException("Too many fields");
}
}
/**
* Add an atttribute to the class.
*
* @param attribute the attribute in raw form
*/
public void addClassAttribute(Element attribute) {
classAttributes.add(attribute);
if (classAttributes.size() > 0xFFFF) {
throw new IllegalArgumentException("Too many class attributes: " + classAttributes.size());
}
}
private int reserveBuf(int size) {
if (buf == null) {
buf = new byte[size];
} else if (bufLen + size > buf.length) {
byte[] newBuf = new byte[Math.max(buf.length * 2, bufLen + size)];
System.arraycopy(buf, 0, newBuf, 0, bufLen);
buf = newBuf;
}
int offset = bufLen;
bufLen += size;
return offset;
}
private void emitElement(Element e) {
int size = e.getSize();
int offset = reserveBuf(size);
int finalOffset = e.copyInto(buf, offset);
if (finalOffset - offset != size) {
throw new Error("Element failed to output the promised bytes: promised " + size + ", got " + (finalOffset - offset));
}
}
private static final char[] noChars = new char[0];
private void emitConstantPool() {
if (rawCP != null) {
int len = rawCP.getRawSize();
int offset = reserveBuf(len);
System.arraycopy(rawCP.getRawBytes(), rawCP.getRawOffset(), buf, offset, len);
}
char[] chars = noChars;
// BE CAREFUL: the newCPEntries array grows during this loop.
for (int i = 0; i < newCPEntries.size(); i++) {
Object o = newCPEntries.get(i);
if (o instanceof CWItem) {
CWItem item = (CWItem) o;
byte t = item.getType();
int offset;
switch (t) {
case CONSTANT_Class:
case CONSTANT_String:
case CONSTANT_MethodType:
offset = reserveBuf(3);
setUShort(buf, offset + 1, addCPUtf8(((CWStringItem) item).s));
break;
case CONSTANT_NameAndType: {
offset = reserveBuf(5);
CWNAT nat = (CWNAT) item;
setUShort(buf, offset + 1, addCPUtf8(nat.n));
setUShort(buf, offset + 3, addCPUtf8(nat.t));
break;
}
case CONSTANT_InvokeDynamic: {
offset = reserveBuf(5);
CWInvokeDynamic inv = (CWInvokeDynamic) item;
setUShort(buf, offset+1, inv.b.getIndexInClassFile());
setUShort(buf, offset+3, addCPNAT(inv.n, inv.t));
break;
}
case CONSTANT_MethodHandle: {
offset = reserveBuf(4);
CWHandle handle = (CWHandle) item;
final byte kind = handle.getKind();
setUByte(buf, offset + 1, kind);
switch (kind) {
case REF_getStatic:
case REF_getField:
case REF_putField:
case REF_putStatic: {
int x = addCPFieldRef(handle.c, handle.n, handle.t);
setUShort(buf, offset + 2, x);
break;
}
case REF_invokeVirtual:
case REF_newInvokeSpecial: {
int x = addCPMethodRef(handle.c, handle.n, handle.t);
setUShort(buf, offset + 2, x);
break;
}
case REF_invokeSpecial:
case REF_invokeStatic: {
int x = addCPMethodRef(handle.c, handle.n, handle.t);
setUShort(buf, offset + 2, x);
break;
}
case REF_invokeInterface: {
int x = addCPInterfaceMethodRef(handle.c, handle.n, handle.t);
setUShort(buf, offset + 2, x);
break;
}
default:
throw new UnsupportedOperationException(String.format("unexpected ref kind %s", kind));
}
break;
}
case CONSTANT_MethodRef:
case CONSTANT_FieldRef:
case CONSTANT_InterfaceMethodRef: {
offset = reserveBuf(5);
CWRef ref = (CWRef) item;
setUShort(buf, offset + 1, addCPClass(ref.c));
setUShort(buf, offset + 3, addCPNAT(ref.n, ref.t));
break;
}
default:
throw new Error("Invalid type: " + t);
}
buf[offset] = t;
} else {
if (o instanceof String) {
String s = (String) o;
int slen = s.length();
if (chars.length < slen) {
chars = new char[slen];
}
s.getChars(0, slen, chars, 0);
int offset = reserveBuf(3);
buf[offset] = CONSTANT_Utf8;
int maxBytes = slen * 3;
int p = reserveBuf(maxBytes); // worst case reservation
for (int j = 0; j < slen; j++) {
char ch = chars[j];
if (ch == 0) {
setUShort(buf, p, 0xC080);
p += 2;
} else if (ch < 0x80) {
buf[p] = (byte) ch;
p += 1;
} else if (ch < 0x800) {
buf[p] = (byte) ((ch >> 6) | 0xC0);
buf[p + 1] = (byte) ((ch & 0x3F) | 0x80);
p += 2;
} else {
buf[p] = (byte) ((ch >> 12) | 0xE0);
buf[p + 1] = (byte) (((ch >> 6) & 0x3F) | 0x80);
buf[p + 2] = (byte) ((ch & 0x3F) | 0x80);
p += 3;
}
}
int bytes = p - (offset + 3);
reserveBuf(bytes - maxBytes); // negative reservation to push back buf
// size
if (bytes > 0xFFFF) {
throw new IllegalArgumentException("String too long: " + bytes + " bytes");
}
setUShort(buf, offset + 1, bytes);
} else if (o instanceof Integer) {
int offset = reserveBuf(5);
buf[offset] = CONSTANT_Integer;
setInt(buf, offset + 1, ((Integer) o).intValue());
} else if (o instanceof Long) {
int offset = reserveBuf(9);
buf[offset] = CONSTANT_Long;
setLong(buf, offset + 1, ((Long) o).longValue());
} else if (o instanceof Float) {
int offset = reserveBuf(5);
buf[offset] = CONSTANT_Float;
setFloat(buf, offset + 1, ((Float) o).intValue());
} else if (o instanceof Double) {
int offset = reserveBuf(9);
buf[offset] = CONSTANT_Double;
setDouble(buf, offset + 1, ((Double) o).intValue());
}
}
}
}
/**
* After you've added everything you need to the class, call this method to generate the actual class file data. This can only be
* called once.
*/
public byte[] makeBytes() throws IllegalArgumentException {
if (buf != null) {
throw new IllegalArgumentException("Can't call makeBytes() twice");
}
if (thisClass == 0) {
throw new IllegalArgumentException("No class name set");
}
reserveBuf(10);
setInt(buf, 0, MAGIC);
setUShort(buf, 4, minorVersion);
setUShort(buf, 6, majorVersion);
emitConstantPool();
// The constant pool can grow during emmission, so store the size last
setUShort(buf, 8, nextCPIndex);
// No new constant pool entries can be allocated; make sure we
// catch any such error by client code
cachedCPEntries = null;
int offset = reserveBuf(8);
setUShort(buf, offset, accessFlags);
setUShort(buf, offset + 2, thisClass);
setUShort(buf, offset + 4, superClass);
if (superInterfaces != null) {
setUShort(buf, offset + 6, superInterfaces.length);
reserveBuf(superInterfaces.length * 2);
for (int i = 0; i < superInterfaces.length; i++) {
setUShort(buf, offset + 8 + i * 2, superInterfaces[i]);
}
} else {
setUShort(buf, offset + 6, 0);
}
offset = reserveBuf(2);
int numFields = fields.size();
setUShort(buf, offset, numFields);
for (int i = 0; i < numFields; i++) {
emitElement(fields.get(i));
}
offset = reserveBuf(2);
int numMethods = methods.size();
// Xiangyu, debug
// System.out.println("numMethods="+numMethods);
setUShort(buf, offset, numMethods);
for (int i = 0; i < numMethods; i++) {
emitElement(methods.get(i));
}
offset = reserveBuf(2);
int numAttrs = classAttributes.size();
setUShort(buf, offset, numAttrs);
for (int i = 0; i < numAttrs; i++) {
emitElement(classAttributes.get(i));
}
if (buf.length == bufLen) {
return buf;
} else {
byte[] b = new byte[bufLen];
System.arraycopy(buf, 0, b, 0, bufLen);
return b;
}
}
/**
* Set the byte at offset 'offset' in 'buf' to the unsigned 8-bit value in v.
*
* @throws IllegalArgumentException if buf is null
*/
public static void setUByte(byte[] buf, int offset, int v) throws IllegalArgumentException {
if (buf == null) {
throw new IllegalArgumentException("buf is null");
}
try {
buf[offset] = (byte) v;
} catch (ArrayIndexOutOfBoundsException e) {
throw new IllegalArgumentException("invalid offset: " + offset, e);
}
}
/**
* Set the 4 bytes at offset 'offset' in 'buf' to the signed 32-bit value in v.
*
* @throws IllegalArgumentException if buf is null
*/
public static void setInt(byte[] buf, int offset, int v) throws IllegalArgumentException {
if (buf == null) {
throw new IllegalArgumentException("buf is null");
}
try {
buf[offset] = (byte) (v >> 24);
buf[offset + 1] = (byte) (v >> 16);
buf[offset + 2] = (byte) (v >> 8);
buf[offset + 3] = (byte) v;
} catch (ArrayIndexOutOfBoundsException e) {
throw new IllegalArgumentException("illegal offset " + offset, e);
}
}
/**
* Set the 8 bytes at offset 'offset' in 'buf' to the signed 64-bit value in v.
*/
public static void setLong(byte[] buf, int offset, long v) throws IllegalArgumentException {
setInt(buf, offset, (int) (v >> 32));
setInt(buf, offset + 4, (int) v);
}
/**
* Set the 4 bytes at offset 'offset' in 'buf' to the float value in v.
*/
public static void setFloat(byte[] buf, int offset, float v) throws IllegalArgumentException {
setInt(buf, offset, Float.floatToIntBits(v));
}
/**
* Set the 8 bytes at offset 'offset' in 'buf' to the double value in v.
*/
public static void setDouble(byte[] buf, int offset, double v) throws IllegalArgumentException {
setLong(buf, offset, Double.doubleToRawLongBits(v));
}
/**
* Set the 2 bytes at offset 'offset' in 'buf' to the unsigned 16-bit value in v.
*
* @throws IllegalArgumentException if buf is null
*/
public static void setUShort(byte[] buf, int offset, int v) throws IllegalArgumentException {
if (buf == null) {
throw new IllegalArgumentException("buf is null");
}
if (offset < 0 || offset + 1 >= buf.length) {
throw new IllegalArgumentException("buf is too short " + buf.length + " " + offset);
}
try {
buf[offset] = (byte) (v >> 8);
buf[offset + 1] = (byte) v;
} catch (ArrayIndexOutOfBoundsException e) {
throw new IllegalArgumentException("invalid offset: " + offset, e);
}
}
}