diff --git a/classpath/avian/LegacyObjectInputStream.java b/classpath/avian/LegacyObjectInputStream.java new file mode 100644 index 0000000000..d9e170fb5f --- /dev/null +++ b/classpath/avian/LegacyObjectInputStream.java @@ -0,0 +1,234 @@ +/* Copyright (c) 2008-2013, Avian Contributors + + Permission to use, copy, modify, and/or distribute this software + for any purpose with or without fee is hereby granted, provided + that the above copyright notice and this permission notice appear + in all copies. + + There is NO WARRANTY for this software. See license.txt for + details. */ + +package java.io; + +import avian.VMClass; + +import java.util.HashMap; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + +public class LegacyObjectInputStream extends InputStream { + private final InputStream in; + private final PushbackReader r; + + public LegacyObjectInputStream(InputStream in) { + this.in = in; + this.r = new PushbackReader(new InputStreamReader(in)); + } + + public int read() throws IOException { + return in.read(); + } + + public int read(byte[] b, int offset, int length) throws IOException { + return in.read(b, offset, length); + } + + public void close() throws IOException { + in.close(); + } + + public Object readObject() throws IOException, ClassNotFoundException { + return readObject(new HashMap()); + } + + public boolean readBoolean() throws IOException { + read('z'); + return readLongToken() != 0; + } + + public byte readByte() throws IOException { + read('b'); + return (byte) readLongToken(); + } + + public char readChar() throws IOException { + read('c'); + return (char) readLongToken(); + } + + public short readShort() throws IOException { + read('s'); + return (short) readLongToken(); + } + + public int readInt() throws IOException { + read('i'); + return (int) readLongToken(); + } + + public long readLong() throws IOException { + read('j'); + return readLongToken(); + } + + public float readFloat() throws IOException { + read('f'); + return (float) readDoubleToken(); + } + + public double readDouble() throws IOException { + read('d'); + return readDoubleToken(); + } + + public void defaultReadObject() throws IOException { + throw new UnsupportedOperationException(); + } + + private void skipSpace() throws IOException { + int c; + while ((c = r.read()) != -1 && Character.isWhitespace((char) c)); + if (c != -1) { + r.unread(c); + } + } + + private void read(char v) throws IOException { + skipSpace(); + + int c = r.read(); + if (c != v) { + if (c == -1) { + throw new EOFException(); + } else { + throw new StreamCorruptedException(); + } + } + } + + private String readStringToken() throws IOException { + skipSpace(); + + StringBuilder sb = new StringBuilder(); + int c; + while ((c = r.read()) != -1 && ! Character.isWhitespace((char) c) && c != ')') { + sb.append((char) c); + } + if (c != -1) { + r.unread(c); + } + return sb.toString(); + } + + private long readLongToken() throws IOException { + return Long.parseLong(readStringToken()); + } + + private double readDoubleToken() throws IOException { + return Double.parseDouble(readStringToken()); + } + + private Object readObject(HashMap map) + throws IOException, ClassNotFoundException + { + skipSpace(); + switch (r.read()) { + case 'a': + return deserializeArray(map); + case 'l': + return deserializeObject(map); + case 'n': + return null; + case -1: + throw new EOFException(); + default: + throw new StreamCorruptedException(); + } + } + + private Object deserialize(HashMap map) + throws IOException, ClassNotFoundException + { + skipSpace(); + switch (r.read()) { + case 'a': + return deserializeArray(map); + case 'l': + return deserializeObject(map); + case 'r': + return map.get((int) readLongToken()); + case 'n': + return null; + case 'z': + return (readLongToken() != 0); + case 'b': + return (byte) readLongToken(); + case 'c': + return (char) readLongToken(); + case 's': + return (short) readLongToken(); + case 'i': + return (int) readLongToken(); + case 'j': + return readLongToken(); + case 'f': + return (float) readDoubleToken(); + case 'd': + return readDoubleToken(); + case -1: + throw new EOFException(); + default: + throw new StreamCorruptedException(); + } + } + + private Object deserializeArray(HashMap map) + throws IOException, ClassNotFoundException + { + read('('); + int id = (int) readLongToken(); + Class c = Class.forName(readStringToken()); + int length = (int) readLongToken(); + Class t = c.getComponentType(); + Object o = Array.newInstance(t, length); + + map.put(id, o); + + for (int i = 0; i < length; ++i) { + Array.set(o, i, deserialize(map)); + } + + read(')'); + + return o; + } + + private static native Object makeInstance(VMClass c); + + private Object deserializeObject(HashMap map) + throws IOException, ClassNotFoundException + { + read('('); + int id = (int) readLongToken(); + Class c = Class.forName(readStringToken()); + Object o = makeInstance(c.vmClass); + + map.put(id, o); + + for (Field f: c.getAllFields()) { + int modifiers = f.getModifiers(); + if ((modifiers & (Modifier.TRANSIENT | Modifier.STATIC)) == 0) { + try { + f.set(o, deserialize(map)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + read(')'); + + return o; + } +} diff --git a/classpath/avian/LegacyObjectOutputStream.java b/classpath/avian/LegacyObjectOutputStream.java new file mode 100644 index 0000000000..7538538322 --- /dev/null +++ b/classpath/avian/LegacyObjectOutputStream.java @@ -0,0 +1,211 @@ +/* Copyright (c) 2008-2013, Avian Contributors + + Permission to use, copy, modify, and/or distribute this software + for any purpose with or without fee is hereby granted, provided + that the above copyright notice and this permission notice appear + in all copies. + + There is NO WARRANTY for this software. See license.txt for + details. */ + +package avian; + +import java.util.IdentityHashMap; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.Serializable; +import java.io.NotSerializableException; + +public class LegacyObjectOutputStream extends OutputStream { + private final PrintStream out; + + public LegacyObjectOutputStream(OutputStream out) { + this.out = new PrintStream(out); + } + + public void write(int c) throws IOException { + out.write(c); + } + + public void write(byte[] b, int offset, int length) throws IOException { + out.write(b, offset, length); + } + + public void flush() throws IOException { + out.flush(); + } + + public void close() throws IOException { + out.close(); + } + + public void writeObject(Object o) throws IOException { + writeObject(o, new IdentityHashMap(), new int[] {0}); + } + + public void writeBoolean(boolean v) { + out.print("z"); + out.print((v ? 1 : 0)); + } + + public void writeByte(byte v) { + out.print("b"); + out.print((int) v); + } + + public void writeChar(char v) { + out.print("c"); + out.print((int) v); + } + + public void writeShort(short v) { + out.print("s"); + out.print((int) v); + } + + public void writeInt(int v) { + out.print("i"); + out.print(v); + } + + public void writeLong(long v) { + out.print("j"); + out.print(v); + } + + public void writeFloat(float v) { + out.print("f"); + out.print(v); + } + + public void writeDouble(double v) { + out.print("d"); + out.print(v); + } + + public void defaultWriteObject() throws IOException { + throw new UnsupportedOperationException(); + } + + private void writeObject(Object o, IdentityHashMap map, + int[] nextId) + throws IOException + { + if (o == null) { + out.print("n"); + } else { + Integer id = map.get(o); + if (id == null) { + map.put(o, nextId[0]); + + Class c = o.getClass(); + if (c.isArray()) { + serializeArray(o, map, nextId); + } else if (Serializable.class.isAssignableFrom(c)) { + serializeObject(o, map, nextId); + } else { + throw new NotSerializableException(c.getName()); + } + } else { + out.print("r"); + out.print(id.intValue()); + } + } + } + + private void serializeArray(Object o, IdentityHashMap map, + int[] nextId) + throws IOException + { + Class c = o.getClass(); + Class t = c.getComponentType(); + int length = Array.getLength(o); + + out.print("a("); + out.print(nextId[0]++); + out.print(" "); + out.print(c.getName()); + out.print(" "); + out.print(length); + + for (int i = 0; i < length; ++i) { + out.print(" "); + Object v = Array.get(o, i); + if (t.equals(boolean.class)) { + writeBoolean((Boolean) v); + } else if (t.equals(byte.class)) { + writeByte((Byte) v); + } else if (t.equals(char.class)) { + writeChar((Character) v); + } else if (t.equals(short.class)) { + writeShort((Short) v); + } else if (t.equals(int.class)) { + writeInt((Integer) v); + } else if (t.equals(long.class)) { + writeLong((Long) v); + } else if (t.equals(float.class)) { + writeFloat((Float) v); + } else if (t.equals(double.class)) { + writeDouble((Double) v); + } else { + writeObject(v, map, nextId); + } + } + + out.print(")"); + } + + private void serializeObject(Object o, IdentityHashMap map, + int[] nextId) + throws IOException + { + Class c = o.getClass(); + + out.print("l("); + out.print(nextId[0]++); + out.print(" "); + out.print(c.getName()); + + for (Field f: c.getAllFields()) { + int modifiers = f.getModifiers(); + if ((modifiers & (Modifier.TRANSIENT | Modifier.STATIC)) == 0) { + out.print(" "); + Object v; + + try { + v = f.get(o); + } catch (Exception e) { + throw new RuntimeException(e); + } + + Class t = f.getType(); + if (t.equals(boolean.class)) { + writeBoolean((Boolean) v); + } else if (t.equals(byte.class)) { + writeByte((Byte) v); + } else if (t.equals(char.class)) { + writeChar((Character) v); + } else if (t.equals(short.class)) { + writeShort((Short) v); + } else if (t.equals(int.class)) { + writeInt((Integer) v); + } else if (t.equals(long.class)) { + writeLong((Long) v); + } else if (t.equals(float.class)) { + writeFloat((Float) v); + } else if (t.equals(double.class)) { + writeDouble((Double) v); + } else { + writeObject(v, map, nextId); + } + } + } + + out.print(")"); + } + +} diff --git a/classpath/java/io/FilterReader.java b/classpath/java/io/FilterReader.java new file mode 100644 index 0000000000..6958d30622 --- /dev/null +++ b/classpath/java/io/FilterReader.java @@ -0,0 +1,51 @@ +/* Copyright (c) 2008-2013, Avian Contributors + + Permission to use, copy, modify, and/or distribute this software + for any purpose with or without fee is hereby granted, provided + that the above copyright notice and this permission notice appear + in all copies. + + There is NO WARRANTY for this software. See license.txt for + details. */ + +package java.io; + +public abstract class FilterReader extends Reader { + protected Reader in; + + protected FilterReader(Reader in) { + this.in = in; + } + + public int read() throws IOException { + return in.read(); + } + + public int read(char[] buffer, int offset, int length) throws IOException { + return in.read(buffer, offset, length); + } + + public boolean ready() throws IOException { + throw new UnsupportedOperationException(); + } + + public long skip(long n) throws IOException { + throw new UnsupportedOperationException(); + } + + public void close() throws IOException { + in.close(); + } + + public boolean markSupported() { + return in.markSupported(); + } + + public void mark(int readAheadLimit) throws IOException { + in.mark(readAheadLimit); + } + + public void reset() throws IOException { + in.reset(); + } +} diff --git a/classpath/java/io/ObjectInputStream.java b/classpath/java/io/ObjectInputStream.java index df3c97682b..b05f6b8339 100644 --- a/classpath/java/io/ObjectInputStream.java +++ b/classpath/java/io/ObjectInputStream.java @@ -10,225 +10,434 @@ package java.io; +import static java.io.ObjectOutputStream.STREAM_MAGIC; +import static java.io.ObjectOutputStream.STREAM_VERSION; +import static java.io.ObjectOutputStream.TC_NULL; +import static java.io.ObjectOutputStream.TC_REFERENCE; +import static java.io.ObjectOutputStream.TC_CLASSDESC; +import static java.io.ObjectOutputStream.TC_OBJECT; +import static java.io.ObjectOutputStream.TC_STRING; +import static java.io.ObjectOutputStream.TC_ARRAY; +import static java.io.ObjectOutputStream.TC_CLASS; +import static java.io.ObjectOutputStream.TC_BLOCKDATA; +import static java.io.ObjectOutputStream.TC_ENDBLOCKDATA; +import static java.io.ObjectOutputStream.TC_RESET; +import static java.io.ObjectOutputStream.TC_BLOCKDATALONG; +import static java.io.ObjectOutputStream.TC_EXCEPTION; +import static java.io.ObjectOutputStream.TC_LONGSTRING; +import static java.io.ObjectOutputStream.TC_PROXYCLASSDESC; +import static java.io.ObjectOutputStream.TC_ENUM; +import static java.io.ObjectOutputStream.SC_WRITE_METHOD; +import static java.io.ObjectOutputStream.SC_BLOCK_DATA; +import static java.io.ObjectOutputStream.SC_SERIALIZABLE; +import static java.io.ObjectOutputStream.SC_EXTERNALIZABLE; +import static java.io.ObjectOutputStream.SC_ENUM; +import static java.io.ObjectOutputStream.getReadOrWriteMethod; + import avian.VMClass; +import java.util.ArrayList; import java.util.HashMap; import java.lang.reflect.Array; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.lang.reflect.Modifier; -public class ObjectInputStream extends InputStream { - private final InputStream in; - private final PushbackReader r; +public class ObjectInputStream extends InputStream implements DataInput { + private final static int HANDLE_OFFSET = 0x7e0000; - public ObjectInputStream(InputStream in) { + private final InputStream in; + private final ArrayList references; + + public ObjectInputStream(InputStream in) throws IOException { this.in = in; - this.r = new PushbackReader(new InputStreamReader(in)); + short signature = (short)rawShort(); + if (signature != STREAM_MAGIC) { + throw new IOException("Unrecognized signature: 0x" + + Integer.toHexString(signature)); + } + int version = rawShort(); + if (version != STREAM_VERSION) { + throw new IOException("Unsupported version: " + version); + } + references = new ArrayList(); } public int read() throws IOException { return in.read(); } + private int rawByte() throws IOException { + int c = read(); + if (c < 0) { + throw new EOFException(); + } + return c; + } + + private int rawShort() throws IOException { + return (rawByte() << 8) | rawByte(); + } + + private int rawInt() throws IOException { + return (rawShort() << 16) | rawShort(); + } + + private long rawLong() throws IOException { + return ((rawInt() & 0xffffffffl) << 32) | rawInt(); + } + + private String rawString() throws IOException { + int length = rawShort(); + byte[] array = new byte[length]; + readFully(array); + return new String(array); + } + public int read(byte[] b, int offset, int length) throws IOException { return in.read(b, offset, length); } + public void readFully(byte[] b) throws IOException { + readFully(b, 0, b.length); + } + + public void readFully(byte[] b, int offset, int length) throws IOException { + while (length > 0) { + int count = read(b, offset, length); + if (count < 0) { + throw new EOFException("Reached EOF " + length + " bytes too early"); + } + offset += count; + length -= count; + } + } + + public String readLine() throws IOException { + int c = read(); + if (c < 0) { + return null; + } else if (c == '\n') { + return ""; + } + StringBuilder builder = new StringBuilder(); + for (;;) { + builder.append((char)c); + c = read(); + if (c < 0 || c == '\n') { + return builder.toString(); + } + } + } + public void close() throws IOException { in.close(); } - public Object readObject() throws IOException, ClassNotFoundException { - return readObject(new HashMap()); + private int remainingBlockData; + + private int rawBlockDataByte() throws IOException { + while (remainingBlockData <= 0) { + int b = rawByte(); + if (b == TC_BLOCKDATA) { + remainingBlockData = rawByte(); + } else { + throw new UnsupportedOperationException("Unknown token: 0x" + + Integer.toHexString(b)); + } + } + --remainingBlockData; + return rawByte(); + } + + private int rawBlockDataShort() throws IOException { + return (rawBlockDataByte() << 8) | rawBlockDataByte(); + } + + private int rawBlockDataInt() throws IOException { + return (rawBlockDataShort() << 16) | rawBlockDataShort(); + } + + private long rawBlockDataLong() throws IOException { + return ((rawBlockDataInt() & 0xffffffffl) << 32) | rawBlockDataInt(); } public boolean readBoolean() throws IOException { - read('z'); - return readLongToken() != 0; + return rawBlockDataByte() != 0; } public byte readByte() throws IOException { - read('b'); - return (byte) readLongToken(); + return (byte)rawBlockDataByte(); } public char readChar() throws IOException { - read('c'); - return (char) readLongToken(); + return (char)rawBlockDataShort(); } public short readShort() throws IOException { - read('s'); - return (short) readLongToken(); + return (short)rawBlockDataShort(); } public int readInt() throws IOException { - read('i'); - return (int) readLongToken(); + return rawBlockDataInt(); } public long readLong() throws IOException { - read('j'); - return readLongToken(); + return rawBlockDataLong(); } public float readFloat() throws IOException { - read('f'); - return (float) readDoubleToken(); + return Float.intBitsToFloat(rawBlockDataInt()); } public double readDouble() throws IOException { - read('d'); - return readDoubleToken(); - } - - public void defaultReadObject() throws IOException { - throw new UnsupportedOperationException(); + return Double.longBitsToDouble(rawBlockDataLong()); } - private void skipSpace() throws IOException { - int c; - while ((c = r.read()) != -1 && Character.isWhitespace((char) c)); - if (c != -1) { - r.unread(c); + public int readUnsignedByte() throws IOException { + return rawBlockDataByte(); + } + + public int readUnsignedShort() throws IOException { + return rawBlockDataShort(); + } + + public String readUTF() throws IOException { + int length = rawBlockDataShort(); + if (remainingBlockData < length) { + throw new IOException("Short block data: " + + remainingBlockData + " < " + length); + } + byte[] bytes = new byte[length]; + readFully(bytes); + remainingBlockData -= length; + return new String(bytes, "UTF-8"); + } + + public int skipBytes(int count) throws IOException { + int i = 0; + while (i < count) { + if (read() < 0) { + return i; + } + ++i; + } + return count; + } + + private static Class charToPrimitiveType(int c) { + if (c == 'B') { + return Byte.TYPE; + } else if (c == 'C') { + return Character.TYPE; + } else if (c == 'D') { + return Double.TYPE; + } else if (c == 'F') { + return Float.TYPE; + } else if (c == 'I') { + return Integer.TYPE; + } else if (c == 'J') { + return Long.TYPE; + } else if (c == 'S') { + return Short.TYPE; + } else if (c == 'Z') { + return Boolean.TYPE; + } + throw new RuntimeException("Unhandled char: " + (char)c); + } + + private void expectToken(int token) throws IOException { + int c = rawByte(); + if (c != token) { + throw new UnsupportedOperationException("Unexpected token: 0x" + + Integer.toHexString(c)); } } - private void read(char v) throws IOException { - skipSpace(); - - int c = r.read(); - if (c != v) { - if (c == -1) { - throw new EOFException(); + private void field(Field field, Object o) + throws IOException, IllegalArgumentException, IllegalAccessException, + ClassNotFoundException + { + Class type = field.getType(); + if (!type.isPrimitive()) { + field.set(o, readObject()); + } else { + if (type == Byte.TYPE) { + field.setByte(o, (byte)rawByte()); + } else if (type == Character.TYPE) { + field.setChar(o, (char)rawShort()); + } else if (type == Double.TYPE) { + field.setDouble(o, Double.longBitsToDouble(rawLong())); + } else if (type == Float.TYPE) { + field.setFloat(o, Float.intBitsToFloat(rawInt())); + } else if (type == Integer.TYPE) { + field.setInt(o, rawInt()); + } else if (type == Long.TYPE) { + field.setLong(o, rawLong()); + } else if (type == Short.TYPE) { + field.setShort(o, (short)rawShort()); + } else if (type == Boolean.TYPE) { + field.setBoolean(o, rawByte() != 0); } else { - throw new StreamCorruptedException(); + throw new IOException("Unhandled type: " + type); } } } - private String readStringToken() throws IOException { - skipSpace(); - - StringBuilder sb = new StringBuilder(); - int c; - while ((c = r.read()) != -1 && ! Character.isWhitespace((char) c) && c != ')') { - sb.append((char) c); - } - if (c != -1) { - r.unread(c); - } - return sb.toString(); - } - - private long readLongToken() throws IOException { - return Long.parseLong(readStringToken()); - } - - private double readDoubleToken() throws IOException { - return Double.parseDouble(readStringToken()); - } - - private Object readObject(HashMap map) - throws IOException, ClassNotFoundException - { - skipSpace(); - switch (r.read()) { - case 'a': - return deserializeArray(map); - case 'l': - return deserializeObject(map); - case 'n': + public Object readObject() throws IOException, ClassNotFoundException { + int c = rawByte(); + if (c == TC_NULL) { return null; - case -1: - throw new EOFException(); - default: - throw new StreamCorruptedException(); + } + if (c == TC_STRING) { + int length = rawShort(); + byte[] bytes = new byte[length]; + readFully(bytes); + String s = new String(bytes, "UTF-8"); + references.add(s); + return s; + } + if (c == TC_REFERENCE) { + int handle = rawInt(); + return references.get(handle - HANDLE_OFFSET); + } + if (c != TC_OBJECT) { + throw new IOException("Unexpected token: 0x" + + Integer.toHexString(c)); + } + + // class desc + c = rawByte(); + ClassDesc classDesc; + if (c == TC_REFERENCE) { + int handle = rawInt() - HANDLE_OFFSET; + classDesc = (ClassDesc)references.get(handle); + } else if (c == TC_CLASSDESC) { + classDesc = classDesc(); + } else { + throw new UnsupportedOperationException("Unexpected token: 0x" + + Integer.toHexString(c)); + } + + try { + Object o = makeInstance(classDesc.clazz.vmClass); + references.add(o); + + do { + Object o1 = classDesc.clazz.cast(o); + boolean customized = (classDesc.flags & SC_WRITE_METHOD) != 0; + Method readMethod = customized ? + getReadOrWriteMethod(o, "readObject") : null; + if (readMethod == null) { + if (customized) { + throw new IOException("Could not find required readObject method " + + "in " + classDesc.clazz); + } + defaultReadObject(o, classDesc.fields); + } else { + current = o1; + currentFields = classDesc.fields; + readMethod.invoke(o, this); + current = null; + currentFields = null; + expectToken(TC_ENDBLOCKDATA); + } + } while ((classDesc = classDesc.superClassDesc) != null); + + return o; + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new IOException(e); } } - private Object deserialize(HashMap map) - throws IOException, ClassNotFoundException - { - skipSpace(); - switch (r.read()) { - case 'a': - return deserializeArray(map); - case 'l': - return deserializeObject(map); - case 'r': - return map.get((int) readLongToken()); - case 'n': - return null; - case 'z': - return (readLongToken() != 0); - case 'b': - return (byte) readLongToken(); - case 'c': - return (char) readLongToken(); - case 's': - return (short) readLongToken(); - case 'i': - return (int) readLongToken(); - case 'j': - return readLongToken(); - case 'f': - return (float) readDoubleToken(); - case 'd': - return readDoubleToken(); - case -1: - throw new EOFException(); - default: - throw new StreamCorruptedException(); - } + private static class ClassDesc { + Class clazz; + int flags; + Field[] fields; + ClassDesc superClassDesc; } - private Object deserializeArray(HashMap map) - throws IOException, ClassNotFoundException - { - read('('); - int id = (int) readLongToken(); - Class c = Class.forName(readStringToken()); - int length = (int) readLongToken(); - Class t = c.getComponentType(); - Object o = Array.newInstance(t, length); + private ClassDesc classDesc() throws ClassNotFoundException, IOException { + ClassDesc result = new ClassDesc(); + String className = rawString(); + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + result.clazz = loader.loadClass(className); + long serialVersionUID = rawLong(); + try { + Field field = result.clazz.getField("serialVersionUID"); + long expected = field.getLong(null); + if (expected != serialVersionUID) { + throw new IOException("Incompatible serial version UID: 0x" + + Long.toHexString(serialVersionUID) + " != 0x" + + Long.toHexString(expected)); + } + } catch (Exception ignored) { } + references.add(result); - map.put(id, o); - - for (int i = 0; i < length; ++i) { - Array.set(o, i, deserialize(map)); + result.flags = rawByte(); + if ((result.flags & ~(SC_SERIALIZABLE | SC_WRITE_METHOD)) != 0) { + throw new UnsupportedOperationException("Cannot handle flags: 0x" + + Integer.toHexString(result.flags)); } - read(')'); + int fieldCount = rawShort(); + result.fields = new Field[fieldCount]; + for (int i = 0; i < result.fields.length; i++) { + int typeChar = rawByte(); + String fieldName = rawString(); + try { + result.fields[i] = result.clazz.getDeclaredField(fieldName); + } catch (Exception e) { + throw new IOException(e); + } + Class type; + if (typeChar == '[' || typeChar == 'L') { + String typeName = (String)readObject(); + if (typeName.startsWith("L") && typeName.endsWith(";")) { + typeName = typeName.substring(1, typeName.length() - 1) + .replace('/', '.'); + } + type = loader.loadClass(typeName); + } else { + type = charToPrimitiveType(typeChar); + } + if (result.fields[i].getType() != type) { + throw new IOException("Unexpected type of field " + fieldName + + ": expected " + result.fields[i].getType() + " but got " + type); + } + } + expectToken(TC_ENDBLOCKDATA); + int c = rawByte(); + if (c == TC_CLASSDESC) { + result.superClassDesc = classDesc(); + } else if (c != TC_NULL) { + throw new UnsupportedOperationException("Unexpected token: 0x" + + Integer.toHexString(c)); + } - return o; + return result; + } + + private Object current; + private Field[] currentFields; + + public void defaultReadObject() throws IOException { + defaultReadObject(current, currentFields); + } + + private void defaultReadObject(Object o, Field[] fields) throws IOException { + try { + for (Field field : fields) { + field(field, o); + } + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new IOException(e); + } } private static native Object makeInstance(VMClass c); - - private Object deserializeObject(HashMap map) - throws IOException, ClassNotFoundException - { - read('('); - int id = (int) readLongToken(); - Class c = Class.forName(readStringToken()); - Object o = makeInstance(c.vmClass); - - map.put(id, o); - - for (Field f: c.getAllFields()) { - int modifiers = f.getModifiers(); - if ((modifiers & (Modifier.TRANSIENT | Modifier.STATIC)) == 0) { - try { - f.set(o, deserialize(map)); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - } - - read(')'); - - return o; - } } diff --git a/classpath/java/io/ObjectOutputStream.java b/classpath/java/io/ObjectOutputStream.java index 0e707e485a..e3c363ceda 100644 --- a/classpath/java/io/ObjectOutputStream.java +++ b/classpath/java/io/ObjectOutputStream.java @@ -10,18 +10,44 @@ package java.io; -import java.util.IdentityHashMap; +import java.util.ArrayList; import java.lang.reflect.Array; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.lang.reflect.Modifier; -public class ObjectOutputStream extends OutputStream { - private final PrintStream out; +public class ObjectOutputStream extends OutputStream implements DataOutput { + final static short STREAM_MAGIC = (short)0xaced; + final static short STREAM_VERSION = 5; + final static byte TC_NULL = (byte)0x70; + final static byte TC_REFERENCE = (byte)0x71; + final static byte TC_CLASSDESC = (byte)0x72; + final static byte TC_OBJECT = (byte)0x73; + final static byte TC_STRING = (byte)0x74; + final static byte TC_ARRAY = (byte)0x75; + final static byte TC_CLASS = (byte)0x76; + final static byte TC_BLOCKDATA = (byte)0x77; + final static byte TC_ENDBLOCKDATA = (byte)0x78; + final static byte TC_RESET = (byte)0x79; + final static byte TC_BLOCKDATALONG = (byte)0x7a; + final static byte TC_EXCEPTION = (byte)0x7b; + final static byte TC_LONGSTRING = (byte)0x7c; + final static byte TC_PROXYCLASSDESC = (byte)0x7d; + final static byte TC_ENUM = (byte)0x7e; + final static byte SC_WRITE_METHOD = 0x01; //if SC_SERIALIZABLE + final static byte SC_BLOCK_DATA = 0x08; //if SC_EXTERNALIZABLE + final static byte SC_SERIALIZABLE = 0x02; + final static byte SC_EXTERNALIZABLE = 0x04; + final static byte SC_ENUM = 0x10; - public ObjectOutputStream(OutputStream out) { - this.out = new PrintStream(out); + private final OutputStream out; + + public ObjectOutputStream(OutputStream out) throws IOException { + this.out = out; + rawShort(STREAM_MAGIC); + rawShort(STREAM_VERSION); } - + public void write(int c) throws IOException { out.write(c); } @@ -38,169 +64,274 @@ public class ObjectOutputStream extends OutputStream { out.close(); } + private void rawByte(int v) throws IOException { + out.write((byte)(v & 0xff)); + } + + private void rawShort(int v) throws IOException { + rawByte(v >> 8); + rawByte(v); + } + + private void rawInt(int v) throws IOException { + rawShort(v >> 16); + rawShort(v); + } + + private void rawLong(long v) throws IOException { + rawInt((int)(v >> 32)); + rawInt((int)(v & 0xffffffffl)); + } + + private void blockData(int... bytes) throws IOException { + blockData(bytes, null, null); + } + + private void blockData(int[] bytes, byte[] bytes2, char[] chars) throws IOException { + int count = (bytes == null ? 0 : bytes.length) + + (bytes2 == null ? 0 : bytes2.length) + + (chars == null ? 0 : chars.length * 2); + if (count < 0x100) { + rawByte(TC_BLOCKDATA); + rawByte(count); + } else { + rawByte(TC_BLOCKDATALONG); + rawInt(count); + } + if (bytes != null) { + for (int b : bytes) { + rawByte(b); + } + } + if (bytes2 != null) { + for (byte b : bytes2) { + rawByte(b & 0xff); + } + } + if (chars != null) { + for (char c : chars) { + rawShort((short)c); + } + } + } + + public void writeBoolean(boolean v) throws IOException { + blockData(v ? 1 : 0); + } + + public void writeByte(int v) throws IOException { + blockData(v); + } + + public void writeShort(int v) throws IOException { + blockData(v >> 8, v); + } + + public void writeChar(int v) throws IOException { + blockData(v >> 8, v); + } + + public void writeInt(int v) throws IOException { + blockData(v >> 24, v >> 16, v >> 8, v); + } + + public void writeLong(long v) throws IOException { + int u = (int)(v >> 32), l = (int)(v & 0xffffffff); + blockData(u >> 24, u >> 16, u >> 8, u, l >> 24, l >> 16, l >> 8, l); + } + + public void writeFloat(float v) throws IOException { + writeInt(Float.floatToIntBits(v)); + } + + public void writeDouble(double v) throws IOException { + writeLong(Double.doubleToLongBits(v)); + } + + public void writeBytes(String s) throws IOException { + blockData(null, s.getBytes(), null); + } + + public void writeChars(String s) throws IOException { + blockData(null, null, s.toCharArray()); + } + + public void writeUTF(String s) throws IOException { + byte[] bytes = s.getBytes(); + int length = bytes.length; + blockData(new int[] { length >> 8, length }, bytes, null); + } + + private int classHandle; + + private void string(String s) throws IOException { + int length = s.length(); + rawShort(length); + for (byte b : s.getBytes()) { + rawByte(b); + } + } + + private static char primitiveTypeChar(Class type) { + if (type == Byte.TYPE) { + return 'B'; + } else if (type == Character.TYPE) { + return 'C'; + } else if (type == Double.TYPE) { + return 'D'; + } else if (type == Float.TYPE) { + return 'F'; + } else if (type == Integer.TYPE) { + return 'I'; + } else if (type == Long.TYPE) { + return 'J'; + } else if (type == Short.TYPE) { + return 'S'; + } else if (type == Boolean.TYPE) { + return 'Z'; + } + throw new RuntimeException("Unhandled primitive type: " + type); + } + + private void classDesc(Class clazz, int scFlags) throws IOException { + rawByte(TC_CLASSDESC); + + // class name + string(clazz.getName()); + + // serial version UID + long serialVersionUID = 1l; + try { + Field field = clazz.getField("serialVersionUID"); + serialVersionUID = field.getLong(null); + } catch (Exception ignored) {} + rawLong(serialVersionUID); + + // handle + rawByte(SC_SERIALIZABLE | scFlags); + + Field[] fields = getFields(clazz); + rawShort(fields.length); + for (Field field : fields) { + Class fieldType = field.getType(); + if (fieldType.isPrimitive()) { + rawByte(primitiveTypeChar(fieldType)); + string(field.getName()); + } else { + rawByte(fieldType.isArray() ? '[' : 'L'); + string(field.getName()); + rawByte(TC_STRING); + string("L" + fieldType.getName().replace('.', '/') + ";"); + } + } + rawByte(TC_ENDBLOCKDATA); // TODO: write annotation + rawByte(TC_NULL); // super class desc + } + + private void field(Object o, Field field) throws IOException { + try { + field.setAccessible(true); + Class type = field.getType(); + if (!type.isPrimitive()) { + writeObject(field.get(o)); + } else if (type == Byte.TYPE) { + rawByte(field.getByte(o)); + } else if (type == Character.TYPE) { + char c = field.getChar(o); + rawShort((short)c); + } else if (type == Double.TYPE) { + double d = field.getDouble(o); + rawLong(Double.doubleToLongBits(d)); + } else if (type == Float.TYPE) { + float f = field.getFloat(o); + rawInt(Float.floatToIntBits(f)); + } else if (type == Integer.TYPE) { + int i = field.getInt(o); + rawInt(i); + } else if (type == Long.TYPE) { + long l = field.getLong(o); + rawLong(l); + } else if (type == Short.TYPE) { + short s = field.getShort(o); + rawShort(s); + } else if (type == Boolean.TYPE) { + boolean b = field.getBoolean(o); + rawByte(b ? 1 : 0); + } else { + throw new UnsupportedOperationException("Field '" + field.getName() + + "' has unsupported type: " + type); + } + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new IOException(e); + } + } + + private static Field[] getFields(Class clazz) { + ArrayList list = new ArrayList(); + Field[] fields = clazz.getDeclaredFields(); + for (Field field : fields) { + if (0 == (field.getModifiers() & + (Modifier.STATIC | Modifier.TRANSIENT))) { + list.add(field); + } + } + return list.toArray(new Field[list.size()]); + } + public void writeObject(Object o) throws IOException { - writeObject(o, new IdentityHashMap(), new int[] {0}); + if (o == null) { + rawByte(TC_NULL); + return; + } + if (o instanceof String) { + byte[] bytes = ((String)o).getBytes("UTF-8"); + rawByte(TC_STRING); + rawShort(bytes.length); + write(bytes); + return; + } + rawByte(TC_OBJECT); + Method writeObject = getReadOrWriteMethod(o, "writeObject"); + if (writeObject == null) { + classDesc(o.getClass(), 0); + defaultWriteObject(o); + } else try { + classDesc(o.getClass(), SC_WRITE_METHOD); + current = o; + writeObject.invoke(o, this); + current = null; + rawByte(TC_ENDBLOCKDATA); + } catch (Exception e) { + throw new IOException(e); + } } - public void writeBoolean(boolean v) { - out.print("z"); - out.print((v ? 1 : 0)); + static Method getReadOrWriteMethod(Object o, String methodName) { + try { + Method method = o.getClass().getDeclaredMethod(methodName, + new Class[] { methodName.startsWith("write") ? + ObjectOutputStream.class : ObjectInputStream.class }); + method.setAccessible(true); + int modifiers = method.getModifiers(); + if ((modifiers & Modifier.STATIC) == 0 || + (modifiers & Modifier.PRIVATE) != 0) { + return method; + } + } catch (NoSuchMethodException ignored) { } + return null; } - public void writeByte(byte v) { - out.print("b"); - out.print((int) v); - } - - public void writeChar(char v) { - out.print("c"); - out.print((int) v); - } - - public void writeShort(short v) { - out.print("s"); - out.print((int) v); - } - - public void writeInt(int v) { - out.print("i"); - out.print(v); - } - - public void writeLong(long v) { - out.print("j"); - out.print(v); - } - - public void writeFloat(float v) { - out.print("f"); - out.print(v); - } - - public void writeDouble(double v) { - out.print("d"); - out.print(v); - } + private Object current; public void defaultWriteObject() throws IOException { - throw new UnsupportedOperationException(); + defaultWriteObject(current); } - - private void writeObject(Object o, IdentityHashMap map, - int[] nextId) - throws IOException - { - if (o == null) { - out.print("n"); - } else { - Integer id = map.get(o); - if (id == null) { - map.put(o, nextId[0]); - Class c = o.getClass(); - if (c.isArray()) { - serializeArray(o, map, nextId); - } else if (Serializable.class.isAssignableFrom(c)) { - serializeObject(o, map, nextId); - } else { - throw new NotSerializableException(c.getName()); - } - } else { - out.print("r"); - out.print(id.intValue()); - } + private void defaultWriteObject(Object o) throws IOException { + for (Field field : getFields(o.getClass())) { + field(o, field); } } - - private void serializeArray(Object o, IdentityHashMap map, - int[] nextId) - throws IOException - { - Class c = o.getClass(); - Class t = c.getComponentType(); - int length = Array.getLength(o); - - out.print("a("); - out.print(nextId[0]++); - out.print(" "); - out.print(c.getName()); - out.print(" "); - out.print(length); - - for (int i = 0; i < length; ++i) { - out.print(" "); - Object v = Array.get(o, i); - if (t.equals(boolean.class)) { - writeBoolean((Boolean) v); - } else if (t.equals(byte.class)) { - writeByte((Byte) v); - } else if (t.equals(char.class)) { - writeChar((Character) v); - } else if (t.equals(short.class)) { - writeShort((Short) v); - } else if (t.equals(int.class)) { - writeInt((Integer) v); - } else if (t.equals(long.class)) { - writeLong((Long) v); - } else if (t.equals(float.class)) { - writeFloat((Float) v); - } else if (t.equals(double.class)) { - writeDouble((Double) v); - } else { - writeObject(v, map, nextId); - } - } - - out.print(")"); - } - - private void serializeObject(Object o, IdentityHashMap map, - int[] nextId) - throws IOException - { - Class c = o.getClass(); - - out.print("l("); - out.print(nextId[0]++); - out.print(" "); - out.print(c.getName()); - - for (Field f: c.getAllFields()) { - int modifiers = f.getModifiers(); - if ((modifiers & (Modifier.TRANSIENT | Modifier.STATIC)) == 0) { - out.print(" "); - Object v; - - try { - v = f.get(o); - } catch (Exception e) { - throw new RuntimeException(e); - } - - Class t = f.getType(); - if (t.equals(boolean.class)) { - writeBoolean((Boolean) v); - } else if (t.equals(byte.class)) { - writeByte((Byte) v); - } else if (t.equals(char.class)) { - writeChar((Character) v); - } else if (t.equals(short.class)) { - writeShort((Short) v); - } else if (t.equals(int.class)) { - writeInt((Integer) v); - } else if (t.equals(long.class)) { - writeLong((Long) v); - } else if (t.equals(float.class)) { - writeFloat((Float) v); - } else if (t.equals(double.class)) { - writeDouble((Double) v); - } else { - writeObject(v, map, nextId); - } - } - } - - out.print(")"); - } - } diff --git a/classpath/java/lang/reflect/Field.java b/classpath/java/lang/reflect/Field.java index 91ad0f9670..401204d23e 100644 --- a/classpath/java/lang/reflect/Field.java +++ b/classpath/java/lang/reflect/Field.java @@ -99,7 +99,7 @@ public class Field extends AccessibleObject { case LongField: return Long.valueOf - ((int) getPrimitive(target, vmField.code, vmField.offset)); + (getPrimitive(target, vmField.code, vmField.offset)); case FloatField: return Float.valueOf @@ -215,6 +215,85 @@ public class Field extends AccessibleObject { } } + private void set(Object instance, long value) + throws IllegalAccessException + { + Object target; + if ((vmField.flags & Modifier.STATIC) != 0) { + target = vmField.class_.staticTable; + } else if (Class.isInstance(vmField.class_, instance)) { + target = instance; + } else { + throw new IllegalArgumentException(); + } + + switch (vmField.code) { + case ByteField: + case BooleanField: + case CharField: + case ShortField: + case IntField: + case LongField: + case FloatField: + case DoubleField: + setPrimitive(target, vmField.code, vmField.offset, value); + break; + + default: + throw new IllegalArgumentException + ("needed " + getType() + ", got primitive type when setting " + + Class.getName(vmField.class_) + "." + getName()); + } + } + + public void setByte(Object instance, byte value) + throws IllegalAccessException + { + set(instance, value & 0xff); + } + + public void setBoolean(Object instance, boolean value) + throws IllegalAccessException + { + set(instance, value ? 1 : 0); + } + + public void setChar(Object instance, char value) + throws IllegalAccessException + { + set(instance, value & 0xffff); + } + + public void setShort(Object instance, short value) + throws IllegalAccessException + { + set(instance, value & 0xffff); + } + + public void setInt(Object instance, int value) + throws IllegalAccessException + { + set(instance, value & 0xffffffffl); + } + + public void setLong(Object instance, long value) + throws IllegalAccessException + { + set(instance, value); + } + + public void setFloat(Object instance, float value) + throws IllegalAccessException + { + set(instance, Float.floatToIntBits(value)); + } + + public void setDouble(Object instance, double value) + throws IllegalAccessException + { + set(instance, Double.doubleToLongBits(value)); + } + private Annotation getAnnotation(Object[] a) { if (a[0] == null) { a[0] = Proxy.newProxyInstance diff --git a/src/classpath-avian.cpp b/src/classpath-avian.cpp index 6d2c5afeb6..52442d0e0c 100644 --- a/src/classpath-avian.cpp +++ b/src/classpath-avian.cpp @@ -307,6 +307,13 @@ Avian_java_io_ObjectInputStream_makeInstance return reinterpret_cast(make(t, c)); } +extern "C" JNIEXPORT int64_t JNICALL +Avian_avian_LegacyObjectInputStream_makeInstance +(Thread* t, object, uintptr_t* arguments) +{ + return Avian_java_io_ObjectInputStream_makeInstance(t, NULL, arguments); +} + extern "C" JNIEXPORT int64_t JNICALL Avian_java_lang_reflect_Field_getPrimitive (Thread* t, object, uintptr_t* arguments) diff --git a/test/Serialize.java b/test/Serialize.java new file mode 100644 index 0000000000..f77b8db4c9 --- /dev/null +++ b/test/Serialize.java @@ -0,0 +1,116 @@ +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +public class Serialize implements Serializable { + public static final long serialVersionUID = 1l; + public int dummy = 0x12345678; + private static void expect(boolean v) { + if (! v) throw new RuntimeException(); + } + + private static void expectEqual(boolean a, boolean b) { + expect(a == b); + } + + private static void expectEqual(int a, int b) { + expect(a == b); + } + + private static void expectEqual(String a, String b) { + expect(a.equals(b)); + } + + protected static void hexdump(byte[] a) { + for (int i = 0; i < a.length; i++) { + if ((i & 0xf) == 0) { + System.err.println(); + } + String hex = Integer.toHexString(a[i] & 0xff); + System.err.print(" " + (hex.length() == 1 ? "0" : "") + hex); + } + System.err.println(); + } + + private static void expectEqual(byte[] a, int[] b) { + expect(a.length == b.length); + + for (int i = 0; i < a.length; ++i) { + expect(a[i] == (byte)b[i]); + } + } + + public static void main(String[] args) throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ObjectOutputStream out2 = new ObjectOutputStream(out); + out2.writeBoolean(true); + out2.flush(); + out2.writeByte(17); + out2.flush(); + out2.writeInt(0xcafebabe); + out2.flush(); + out2.writeUTF("Max & Möritz"); + out2.flush(); + out2.writeChar('ɛ'); + out2.flush(); + out2.writeObject(new Serialize()); + out2.close(); + byte[] array = out.toByteArray(); + expectEqual(array, new int[] { + // magic + 0xac, 0xed, + // version + 0x00, 0x05, + // blockdata, length + 0x77, 0x1, + // true + 1, + // blockdata, length + 0x77, 0x1, + // (byte)17 + 17, + // blockdata, length + 0x77, 0x4, + // 0xcafebabe + 0xca, 0xfe, 0xba, 0xbe, + // blockdata, length + 0x77, 0xf, + // "Max & Möritz" + 0x0, 0xd, 'M', 'a', 'x', ' ', '&', ' ', 'M', 0xc3, 0xb6, 'r', 'i', 't', 'z', + // blockdata, length + 0x77, 0x2, + // 'ö' + 0x02, 0x5b, + // object + 0x73, + // class desc, "Serialize" + 0x72, 0, 9, 'S', 'e', 'r', 'i', 'a', 'l', 'i', 'z', 'e', + // serialVersionUID + 0, 0, 0, 0, 0, 0, 0, 1, + // flags (SC_SERIALIZABLE) + 2, + // field count + 0x0, 0x1, + // int dummy + 'I', 0x0, 0x5, 'd', 'u', 'm', 'm', 'y', + // class annotation + 0x78, + // super class desc + 0x70, + // classdata[] + 0x12, 0x34, 0x56, 0x78 + }); + ByteArrayInputStream in = new ByteArrayInputStream(array); + ObjectInputStream in2 = new ObjectInputStream(in); + expectEqual(true, in2.readBoolean()); + expectEqual(17, in2.readByte()); + expectEqual(0xcafebabe, in2.readInt()); + expectEqual("Max & Möritz", in2.readUTF()); + expectEqual('ɛ', in2.readChar()); + Serialize unserialized = (Serialize) in2.readObject(); + expectEqual(0x12345678, unserialized.dummy); + in2.close(); + } +}