简体   繁体   中英

How to bypass ClassNotFoundException while ObjectInputStream.readObject?

I'm trying to read a serialized Java file containing instances of classes I don't have in my classpath while reading.

Is there a way (perhaps by writing my own ObjectInputStream ?) to ignore those ClassNotFoundException and replace the corresponding object of the stream by null ?

The object I want to read is similar to this one :

public class Log {
    private String someField;
    private Throwable throwable;
}

Actually, that Log object is read, but I don't have in my classpath the concrete class of some Log.throwable values. I would want that in that case, the throwable field value would be null but I want my Log object with the other fields read.

If I catch the exception, I couldn't even have my Log object.

Actually, I have tried multiple way to do this (extend ObjectInputStream and implement ObjectInputStream.readClassDescriptor() in order to return a Proxy of an ObjectStreamClass which would return null for default method ObjectStreamClass.getResolveException() , using Javassist because JDK cannot proxify classes, but the problem is : ObjectStreamClass cannot be instantiated outside of java.io package).

But I finally found a (rather ugly) way to do this :

public class DecompressibleObjectInputStream extends ObjectInputStream {
    private static Logger logger = LoggerFactory.getLogger(DecompressibleObjectInputStream.class);

    public DecompressibleObjectInputStream(InputStream in) throws IOException {
        super(in);

        try {
            // activating override on readObject thanks to https://stackoverflow.com/a/3301720/535203
            Field enableOverrideField = ObjectInputStream.class.getDeclaredField("enableOverride");

            enableOverrideField.setAccessible(true);

            Field fieldModifiersField = Field.class.getDeclaredField("modifiers");
            fieldModifiersField.setAccessible(true);
            fieldModifiersField.setInt(enableOverrideField, enableOverrideField.getModifiers() & ~Modifier.FINAL);

            enableOverrideField.set(this, true);
        } catch (NoSuchFieldException e) {
            warnCantOverride(e);
        } catch (SecurityException e) {
            warnCantOverride(e);
        } catch (IllegalArgumentException e) {
            warnCantOverride(e);
        } catch (IllegalAccessException e) {
            warnCantOverride(e);
        }
    }

    private void warnCantOverride(Exception e) {
        logger.warn("Couldn't enable readObject override, won't be able to avoid ClassNotFoundException while reading InputStream", e);
    }
    @Override
    public void defaultReadObject() throws IOException, ClassNotFoundException {
        try {
            super.defaultReadObject();
        } catch (ClassNotFoundException e) {
            logger.warn("Potentially Fatal Deserialization Operation.", e);
        }
    }

    @Override
    protected Object readObjectOverride() throws IOException, ClassNotFoundException {
    // copy of JDK 7 code avoiding the ClassNotFoundException to be thrown :
        /*
            // if nested read, passHandle contains handle of enclosing object
        int outerHandle = passHandle;
        try {
            Object obj = readObject0(false);
            handles.markDependency(outerHandle, passHandle);
            ClassNotFoundException ex = handles.lookupException(passHandle);
            if (ex != null) {
                throw ex;
            }
            if (depth == 0) {
                vlist.doCallbacks();
            }
            return obj;
        } finally {
            passHandle = outerHandle;
            if (closed && depth == 0) {
                clear();
            }
        }
         */
        try {
        int outerHandle = getObjectInputStreamFieldValue("passHandle");
        int depth = getObjectInputStreamFieldValue("depth");
        try {
            Object obj = callObjectInputStreamMethod("readObject0", new Class<?>[] {boolean.class}, false);
            Object handles = getObjectInputStreamFieldValue("handles");
            Object passHandle = getObjectInputStreamFieldValue("passHandle");
            callMethod(handles, "markDependency", new Class<?>[] {int.class, int.class}, outerHandle, passHandle);

            ClassNotFoundException ex = callMethod(handles, "lookupException", new Class<?>[] {int.class},  passHandle);

            if (ex != null) {
                logger.warn("Avoiding exception", ex);
            }
            if (depth == 0) {
                callMethod(getObjectInputStreamFieldValue("vlist"), "doCallbacks", new Class<?>[] {});
            }
            return obj;
        } finally {
            getObjectInputStreamField("passHandle").setInt(this, outerHandle);
            boolean closed = getObjectInputStreamFieldValue("closed");
            if (closed && depth == 0) {
                callObjectInputStreamMethod("clear", new Class<?>[] {});
            }
        }
        } catch (NoSuchFieldException e) {
            throw createCantMimicReadObject(e);
        } catch (SecurityException e) {
            throw createCantMimicReadObject(e);
        } catch (IllegalArgumentException e) {
            throw createCantMimicReadObject(e);
        } catch (IllegalAccessException e) {
            throw createCantMimicReadObject(e);
        } catch (InvocationTargetException e) {
            throw createCantMimicReadObject(e);
        } catch (NoSuchMethodException e) {
            throw createCantMimicReadObject(e);
        } catch (Throwable t) {
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            }
            if (t instanceof IOException) {
                throw (IOException)t;
            }
            throw createCantMimicReadObject(t);
        }
    }

    private IllegalStateException createCantMimicReadObject(Throwable t) {
        return new IllegalStateException("Can't mimic JDK readObject method", t);
    }

    @SuppressWarnings("unchecked")
    private <T> T getObjectInputStreamFieldValue(String fieldName) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
        Field declaredField = getObjectInputStreamField(fieldName);
        return (T) declaredField.get(this);
    }

    private Field getObjectInputStreamField(String fieldName) throws NoSuchFieldException {
        Field declaredField = ObjectInputStream.class.getDeclaredField(fieldName);
        declaredField.setAccessible(true);
        return declaredField;
    }

    @SuppressWarnings("unchecked")
    private <T> T callObjectInputStreamMethod(String methodName, Class<?>[] parameterTypes, Object... args) throws Throwable {
        Method declaredMethod = ObjectInputStream.class.getDeclaredMethod(methodName, parameterTypes);
        declaredMethod.setAccessible(true);
        try {
            return (T) declaredMethod.invoke(this, args);
        } catch (InvocationTargetException e) {
            throw e.getCause();
        }
    }

    @SuppressWarnings("unchecked")
    private <T> T callMethod(Object object, String methodName, Class<?>[] parameterTypes, Object... args) throws Throwable {
        Method declaredMethod = object.getClass().getDeclaredMethod(methodName, parameterTypes);
        declaredMethod.setAccessible(true);
        try {
            return (T) declaredMethod.invoke(object, args);
        } catch (InvocationTargetException e) {
            throw e.getCause();
        }
    }
}

Then I overrode the ObjectInputStream.readClassDescriptor() in order to ignore differences between serialVersionUID also (as described in that answer ) and I've got an ObjectInputStream which can read nearly everything !

I don't think there is a way to do this ... apart from cloning and modifying the Java serialization implementation.

Certainly, readObject and readResolve hooks won't help, because they rely on methods of the class that you cannot load.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM