NotSerializable object issue...

S

Slash

Hello all...

I have got a ghost bug running around my project* for a while now, and
I haven't been able to track it... to put it simple, I am writing a
serializable object to disk (an object of class Game) when the player
saves the game. It works most of the times, however, under
circumstances I haven't been able to figure out, it fails with a
NotSerializable Exception over an object that is supposed never to be
Serialized. (GFXUserInterface, which I dont want to just implement
Serializable, as I would then have to make its members Serializable,
and it would be an ugly hack)

I have looked for member variables of this type and its
superclasses... all of them are transient. I did the same for all the
interfaces implemented by this type... no luck...

Can you recomend me a program that recursively checks a serializable
class to see if it has non transient non serializable fields? I tried
FindBugs, but it didn't report anything about it...

Thanks in advance for any help that could be provided.
 
P

petersprc

Does the stack trace give a hint as to what nested object is
requesting the invalid serialization?

Not sure if a class diagram might help (umlet.com). Eclipse might have
some additional tools to help.
 
B

Bjorn Borud

[Slash <[email protected]>]
|
| Thanks in advance for any help that could be provided.

using the Serializable interface is not really a good idea. I would
rather try to re-design things in a way that separates state out into
objects that you can convert to a serialized form, and read back in,
in a more controlled manner. if you use Serializable you are
complicating things for those who want to extend your code.

-Bjørn
 
T

Tom Hawtin

Slash said:
I have got a ghost bug running around my project* for a while now, and
I haven't been able to track it... to put it simple, I am writing a
serializable object to disk (an object of class Game) when the player
saves the game. It works most of the times, however, under
circumstances I haven't been able to figure out, it fails with a
NotSerializable Exception over an object that is supposed never to be
Serialized. (GFXUserInterface, which I dont want to just implement
Serializable, as I would then have to make its members Serializable,
and it would be an ugly hack)

I have looked for member variables of this type and its
superclasses... all of them are transient. I did the same for all the
interfaces implemented by this type... no luck...

Even Object?
Can you recomend me a program that recursively checks a serializable
class to see if it has non transient non serializable fields? I tried
FindBugs, but it didn't report anything about it...

That's quite tough to do statically. Fields don't usually have the
static type information to force the object to be Serializable. One day
we will have better static type checking...

You are quite possibly serialising objects you didn't intend to. For
instance, you might be serialising inner classes (never a good idea to
serialise nested classes).

The first thing I would do is run strings over your serialised data, or
load it into a text editor that wont barf on binary content. Look for
class names that you didn't expect to be there. You could also subclass
ObjectOutputStream and override annotateClass and annotateProxyClass.

IIRC, there is an option in 1.6 to give more debug information - check
the source.

Tom Hawtin
 
A

adamcrume

Hello all...

I have got a ghost bug running around my project* for a while now, and
I haven't been able to track it... to put it simple, I am writing a
serializable object to disk (an object of class Game) when the player
saves the game. It works most of the times, however, under
circumstances I haven't been able to figure out, it fails with a
NotSerializable Exception over an object that is supposed never to be
Serialized. (GFXUserInterface, which I dont want to just implement
Serializable, as I would then have to make its members Serializable,
and it would be an ugly hack)

I have looked for member variables of this type and its
superclasses... all of them are transient. I did the same for all the
interfaces implemented by this type... no luck...

Can you recomend me a program that recursively checks a serializable
class to see if it has non transient non serializable fields? I tried
FindBugs, but it didn't report anything about it...

Thanks in advance for any help that could be provided.

I found a great class on this page:
http://herebebeasties.com/2007-02-08/javaionotserializableexception-in-your-httpsession/
(full source code here:
http://svn.apache.org/viewvc/incuba...zableChecker.java?revision=532837&view=markup).
It gives you a stack showing the path from the main object being
serialized to the unserializable object.

I've modified it to remove Wicket-specific stuff, and made a few minor
improvements, too. Here's my version:


/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version
2.0
* (the "License"); you may not use this file except in compliance
with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import java.io.Externalizable;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io_ObjectOutput;
import java.io_ObjectOutputStream;
import java.io_ObjectStreamClass;
import java.io_ObjectStreamField;
import java.io_OutputStream;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;


/**
* Utility class that analyzes objects for non-serializable nodes.
Construct
* with the object you want to check, and then call {@link
#writeObject(Object)}. When a
* non-serializable object is found, a {@link
NotSerializableException} is
* thrown with a message that shows the trace up to the not-
serializable object.
* The exception is thrown for the first non-serializable instance it
* encounters, so multiple problems will not be shown.
* <p>
* As this class depends heavily on JDK's serialization internals
using
* introspection, analyzing may not be possible, for instance when the
runtime
* environment does not have sufficient rights to set fields
accessible that
* would otherwise be hidden. You should call
* {@link SerializableChecker#isAvailable()} to see whether this class
can
* operate properly. If it doesn't, you should fall back to e.g. re-
throwing/
* printing the {@link NotSerializableException} you probably got
before using
* this class.
* </p>
*
* <p>
* Modified by Adam Crume to remove all Wicket-specific logic and add
various minor improvements.
* </p>
*
* @author eelcohillenius
* @author Al Maw
* @author Adam Crume
*/
public class SerializableChecker extends ObjectOutputStream {
private static final NoopOutputStream DUMMY_OUTPUT_STREAM = new
NoopOutputStream();

/** Whether we can execute the tests. If false, check will just
return. */
private static boolean available = true;

// this hack - accessing the serialization API through introspection
- is
// the only way to use Java serialization for our purposes without
writing
// the whole thing from scratch (and even then, it would be limited).
This
// way of working is of course fragile for internal API changes, but
as we
// do an extra check on availability and we report when we can't use
this
// introspection fu, we'll find out soon enough and clients on this
class
// can fall back on Java's default exception for serialization errors
(which
// sucks and is the main reason for this attempt).
private static final Method lookup;

private static final Method getClassDataLayoutMethod;

private static final Method getNumObjFields;

private static final Method getObjFieldValues;

private static final Method fieldMethod;

static {
try {
lookup = ObjectStreamClass.class.getDeclaredMethod("lookup", new
Class[] {Class.class, Boolean.TYPE});
lookup.setAccessible(true);

getClassDataLayoutMethod =
ObjectStreamClass.class.getDeclaredMethod("getClassDataLayout", null);
getClassDataLayoutMethod.setAccessible(true);

getNumObjFields =
ObjectStreamClass.class.getDeclaredMethod("getNumObjFields", null);
getNumObjFields.setAccessible(true);

getObjFieldValues =
ObjectStreamClass.class.getDeclaredMethod("getObjFieldValues", new
Class[] {Object.class, Object[].class});
getObjFieldValues.setAccessible(true);

fieldMethod = ObjectStreamField.class.getDeclaredMethod("getField",
null);
fieldMethod.setAccessible(true);
} catch(SecurityException e) {
available = false;
throw new RuntimeException(e);
} catch(NoSuchMethodException e) {
available = false;
throw new RuntimeException(e);
}
}


/**
* Gets whether we can execute the tests. If false, calling {@link
#check(Object)}
* will just return and you are advised to rely on the
* {@link NotSerializableException}. Clients are advised to call this
* method prior to calling the check method.
*
* @return whether security settings and underlying API etc allow for
* accessing the serialization API using introspection
*/
public static boolean isAvailable() {
return available;
}

/** object stack that with the trace path. */
private final LinkedList traceStack = new LinkedList();

/** set for checking circular references. */
private final HandleTable checked = new HandleTable(10, (float)3.00);

/** root object being analyzed. */
private Object root;

/** cache for classes - writeObject methods. */
private Map writeObjectMethodCache = new HashMap();

/** current full field description. */
private StringBuffer fieldDescription;


public SerializableChecker() throws IOException {
}


/**
* @see java.io_ObjectOutputStream#reset()
*/
public void reset() throws IOException {
root = null;
checked.clear();
fieldDescription = null;
traceStack.clear();
writeObjectMethodCache.clear();
}


private void check(final Object obj) throws IOException {
if(obj == null) {
return;
}

Class cls = obj.getClass();
traceStack.add(new TraceSlot(obj, fieldDescription));

if(!(obj instanceof Serializable)) {
throw new NotSerializableException(toPrettyPrintedStack(cls));
}

final ObjectStreamClass desc;
try {
desc = (ObjectStreamClass)lookup.invoke(null, new Object[] {cls,
Boolean.TRUE});
} catch(IllegalAccessException e) {
throw new RuntimeException(e);
} catch(InvocationTargetException e) {
throw new RuntimeException(e);
}

if(cls.isPrimitive()) {
// skip
} else if(cls.isArray()) {
checked.assign(obj);
Class ccl = cls.getComponentType();
if(!(ccl.isPrimitive())) {
Object[] objs = (Object[])obj;
for(int i = 0; i < objs.length; i++) {
fieldDescription.append('[').append(i).append(']');
check(objs);
}
}
} else if(obj instanceof Externalizable && !Proxy.isProxyClass(cls))
{
Externalizable extObj = (Externalizable)obj;
extObj.writeExternal(new ObjectOutputAdaptor() {
private int count = 0;


public void writeObject(Object streamObj) throws IOException {
// Check for circular reference.
if(checked.contains(streamObj)) {
return;
}

checked.assign(streamObj);
fieldDescription.append("[write:").append(count++).append(']');

check(streamObj);
}
});
} else {
Method writeObjectMethod = null;
Object o = writeObjectMethodCache.get(cls);
if(o != null) {
if(o instanceof Method) {
writeObjectMethod = (Method)o;
}
} else {
try {
writeObjectMethod = cls.getDeclaredMethod("writeObject", new
Class[] {java.io_ObjectOutputStream.class});
} catch(SecurityException e) {
// we can't access/ set accessible to true
writeObjectMethodCache.put(cls, Boolean.FALSE);
} catch(NoSuchMethodException e) {
// cls doesn't have that method
writeObjectMethodCache.put(cls, Boolean.FALSE);
}
}

if(writeObjectMethod != null) {
class InterceptingObjectOutputStream extends ObjectOutputStream {
private int counter;


InterceptingObjectOutputStream() throws IOException {
super(DUMMY_OUTPUT_STREAM);
enableReplaceObject(true);
}


protected Object replaceObject(Object streamObj) throws
IOException {
if(streamObj == obj) {
return streamObj;
}

counter++;
// Check for circular reference.
if(checked.contains(streamObj)) {
return null;
}

checked.assign(obj);
fieldDescription.append("[write:").append(counter).append(']');
check(streamObj);
return streamObj;
}
}
InterceptingObjectOutputStream ioos = new
InterceptingObjectOutputStream();
ioos.writeObject(obj);
} else {
Object[] slots;
try {
slots = (Object[])getClassDataLayoutMethod.invoke(desc, null);
} catch(Exception e) {
throw new RuntimeException(e);
}
for(int i = 0; i < slots.length; i++) {
ObjectStreamClass slotDesc;
try {
Field descField = slots.getClass().getDeclaredField("desc");
descField.setAccessible(true);
slotDesc = (ObjectStreamClass)descField.get(slots);
} catch(Exception e) {
throw new RuntimeException(e);
}
checked.assign(obj);
checkFields(obj, slotDesc);
}
}
}

traceStack.removeLast();
}


private void checkFields(Object obj, ObjectStreamClass desc) throws
IOException {
int numFields;
try {
numFields = ((Integer)getNumObjFields.invoke(desc,
null)).intValue();
} catch(IllegalAccessException e) {
throw new RuntimeException(e);
} catch(InvocationTargetException e) {
throw new RuntimeException(e);
}

if(numFields > 0) {
ObjectStreamField[] fields = desc.getFields();
Object[] objVals = new Object[numFields];
int numPrimFields = fields.length - objVals.length;
try {
getObjFieldValues.invoke(desc, new Object[] {obj, objVals});
} catch(IllegalAccessException e) {
throw new RuntimeException(e);
} catch(InvocationTargetException e) {
throw new RuntimeException(e);
}
for(int i = 0; i < objVals.length; i++) {
Object val = objVals;
if(val instanceof String || val instanceof Boolean || val
instanceof Class) {
// filter out common cases
continue;
}

// Check for circular reference.
if(checked.contains(val)) {
continue;
}

ObjectStreamField fieldDesc = fields[numPrimFields + i];
Field field;
try {
field = (Field)fieldMethod.invoke(fieldDesc, null);
} catch(IllegalAccessException e) {
throw new RuntimeException(e);
} catch(InvocationTargetException e) {
throw new RuntimeException(e);
}

fieldDescription = new StringBuffer(field.toString());
check(val);
}
}
}


/**
* Dump with identation.
*
* @param type
* the type that couldn't be serialized
* @return A very pretty dump
*/
private final String toPrettyPrintedStack(Class type) {
StringBuffer result = new StringBuffer();
StringBuffer spaces = new StringBuffer();
result.append("Unable to serialize class: ");
result.append(type.getName());
result.append("\nField hierarchy is:");
for(Iterator i = traceStack.listIterator(); i.hasNext();) {
spaces.append(" ");
TraceSlot slot = (TraceSlot)i.next();
result.append('\n').append(spaces).append(slot.fieldDescription);
result.append(" [class=").append(slot.object.getClass().getName());
result.append(']');
}
result.append(" <----- field that is not serializable");
return result.toString();
}


/**
* @see
java.io_ObjectOutputStream#writeObjectOverride(java.lang.Object)
*/
protected final void writeObjectOverride(Object obj) throws
IOException {
if(!available) {
return;
}
root = obj;

check(root);
}


public void flush() throws IOException {
}


public void close() throws IOException {
}


/**
* Lightweight identity hash table which maps objects to integer
handles,
* assigned in ascending order (comes from {@link
ObjectOutputStream}).
*/
private static class HandleTable {
/** number of mappings in table/next available handle */
private int size;

/** size threshold determining when to expand hash spine */
private int threshold;

/** factor for computing size threshold */
private final float loadFactor;

/** maps hash value -> candidate handle value */
private int[] spine;

/** maps handle value -> next candidate handle value */
private int[] next;

/** maps handle value -> associated object */
private Object[] objs;


/**
* Construct.
*/
public HandleTable() {
this(16, 0.75f);
}


/**
* Construct.
*
* @param initialCapacity
* @param loadFactor
*/
public HandleTable(int initialCapacity, float loadFactor) {
this.loadFactor = loadFactor;
spine = new int[initialCapacity];
next = new int[initialCapacity];
objs = new Object[initialCapacity];
threshold = (int)(initialCapacity * loadFactor);
clear();
}


/**
* Assigns next available handle to given object, and returns handle
value.
* Handles are assigned in ascending order starting at 0.
*
* @param obj
* @return
*/
public int assign(Object obj) {
if(size >= next.length) {
growEntries();
}
if(size >= threshold) {
growSpine();
}
insert(obj, size);
return size++;
}


/**
* Clears this table.
*/
public void clear() {
Arrays.fill(spine, -1);
Arrays.fill(objs, 0, size, null);
size = 0;
}


/**
* Whether this table contains the provided object.
*
* @param obj
* object to check
* @return whether it contains the provided object
*/
public boolean contains(Object obj) {
return lookup(obj) != -1;
}


/**
* Looks up and returns handle associated with given object, or -1
if no
* mapping found.
*
* @param obj
* @return
*/
public int lookup(Object obj) {
if(size == 0) {
return -1;
}
int index = hash(obj) % spine.length;
for(int i = spine[index]; i >= 0; i = next) {
if(objs == obj) {
return i;
}
}
return -1;
}


/**
* @return The number of elements
*/
public int size() {
return size;
}


private void growEntries() {
int newLength = (next.length << 1) + 1;
int[] newNext = new int[newLength];
System.arraycopy(next, 0, newNext, 0, size);
next = newNext;

Object[] newObjs = new Object[newLength];
System.arraycopy(objs, 0, newObjs, 0, size);
objs = newObjs;
}


private void growSpine() {
spine = new int[(spine.length << 1) + 1];
threshold = (int)(spine.length * loadFactor);
Arrays.fill(spine, -1);
for(int i = 0; i < size; i++) {
insert(objs, i);
}
}


private int hash(Object obj) {
return System.identityHashCode(obj) & 0x7FFFFFFF;
}


private void insert(Object obj, int handle) {
int index = hash(obj) % spine.length;
objs[handle] = obj;
next[handle] = spine[index];
spine[index] = handle;
}
}


/**
* Does absolutely nothing.
*/
private static class NoopOutputStream extends OutputStream {
public void close() {
}


public void flush() {
}


public void write(byte[] b) {
}


public void write(byte[] b, int i, int l) {
}


public void write(int b) {
}
}


private static abstract class ObjectOutputAdaptor implements
ObjectOutput {
public void close() throws IOException {
}


public void flush() throws IOException {
}


public void write(byte[] b) throws IOException {
}


public void write(byte[] b, int off, int len) throws IOException {
}


public void write(int b) throws IOException {
}


public void writeBoolean(boolean v) throws IOException {
}


public void writeByte(int v) throws IOException {
}


public void writeBytes(String s) throws IOException {
}


public void writeChar(int v) throws IOException {
}


public void writeChars(String s) throws IOException {
}


public void writeDouble(double v) throws IOException {
}


public void writeFloat(float v) throws IOException {
}


public void writeInt(int v) throws IOException {
}


public void writeLong(long v) throws IOException {
}


public void writeShort(int v) throws IOException {
}


public void writeUTF(String str) throws IOException {
}
}


/** Holds information about the field and the resulting object being
traced. */
private static final class TraceSlot {
private final String fieldDescription;

private final Object object;


TraceSlot(Object object, StringBuffer fieldDescription) {
this.object = object;
this.fieldDescription = fieldDescription==null?
null:fieldDescription.toString();
}


public String toString() {
return object.getClass() + " - " + fieldDescription;
}
}
}
 
T

Twisted

It gives you a stack showing the path from the main object being
serialized to the unserializable object.

This should really be a standard Java feature, included in the detail
message of a NotSerializableException automatically. I don't see any
reason the detail message can't be arbitrarily long and have tab and
linefeed characters, so it can even be formatted as nicely as the
stack trace further below the exception. It could replace it for that
matter -- as a general rule, the key to debugging this particular
exception is the object graph structure and not where in the code it
was thrown. The error normally lies elsewhere, in a serializable class
somewhere or in code that adds objects to a collection. Usually
there's only one or a few places in your code where objects are
serialized anyway. Of course, making sure you use generics and make
collections you intend to serialize be
SomeCollectionType<SomeSerializableType> is good defensive practise.

Actually, it occurs to me that this might be obviated by the following
practises:
* Use generics.
* When generic container objects exist that you plan to serialize,
make sure they have a Serializable subtype as type parameter.
* With your serializable classes, make sure every field is at least
one of: transient; of a type that implements Serializable.
* Now the only source of NotSerializableException should be either
- Serializing the wrong object at a point in your own code that
explicitly writes an object, and the exception appears right there; or
- Some perverse class inherits Serializable by what it implements or
extends, and makes itself non-serializable by overriding some
serialization methods (likely writeObject) to throw
NotSerializableException. The exception stack trace will point the
finger
squarely at the guilty class. If it's yours, fix it. If it's not,
then you have the problem of determining how it got into the
serialized object graph...back to square one, only this way it
only ever happens if someone did something perverse. They made
serializability inherited for a reason you know.
 
S

Slash

Even Object?

To say the truth, I stoped at my project boundaries :)
That's quite tough to do statically. Fields don't usually have the
static type information to force the object to be Serializable. One day
we will have better static type checking...

I hope so :)
You are quite possibly serialising objects you didn't intend to. For
instance, you might be serialising inner classes (never a good idea to
serialise nested classes).

That really gives a clue; GFXUserInterface has an inner class which is
serialized... weird thing it doesnt always tries to serialize its
owner class? :S I will check

SNIP
You could also subclass
ObjectOutputStream and override annotateClass and annotateProxyClass.

That would be very handy, thank you for the info
IIRC, there is an option in 1.6 to give more debug information - check
the source.

Thanks Tom
 
S

Slash

I found a great class on this page:http://herebebeasties.com/2007-02-08/javaionotserializableexception-i...
(full source code here:http://svn.apache.org/viewvc/incubator/wicket/trunk/jdk-1.4/wicket/sr...).
It gives you a stack showing the path from the main object being
serialized to the unserializable object.

I've modified it to remove Wicket-specific stuff, and made a few minor
improvements, too. Here's my version:

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version
2.0
* (the "License"); you may not use this file except in compliance
with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import java.io.Externalizable;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io_ObjectOutput;
import java.io_ObjectOutputStream;
import java.io_ObjectStreamClass;
SNIP

Definitively handy... thanks much!
 
D

Daniel Pitts

Hello all...

I have got a ghost bug running around my project* for a while now, and
I haven't been able to track it... to put it simple, I am writing a
serializable object to disk (an object of class Game) when the player
saves the game. It works most of the times, however, under
circumstances I haven't been able to figure out, it fails with a
NotSerializable Exception over an object that is supposed never to be
Serialized. (GFXUserInterface, which I dont want to just implement
Serializable, as I would then have to make its members Serializable,
and it would be an ugly hack)

I have looked for member variables of this type and its
superclasses... all of them are transient. I did the same for all the
interfaces implemented by this type... no luck...

Can you recomend me a program that recursively checks a serializable
class to see if it has non transient non serializable fields? I tried
FindBugs, but it didn't report anything about it...

Thanks in advance for any help that could be provided.

Any field you don't want serialized should be declared transient.

It also sounds like a design flaw to hold on to an object that is
related to the running state of your program, but not related to the
state of the game.
 
R

Roedy Green

Can you recomend me a program that recursively checks a serializable
class to see if it has non transient non serializable fields? I tried
FindBugs, but it didn't report anything about it...

You have some Object and Interface references that point to objects
whose class code you did not write. You might use a global search to
find such references. They you could write some tree spanning code
that chased references in your Game object. It could then as if they
were instanceof Serializable. That way you could nail down the
offending object and the offending reference.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,770
Messages
2,569,583
Members
45,074
Latest member
StanleyFra

Latest Threads

Top