-
Bug
-
Resolution: Incomplete
-
P4
-
8, 17, 18, 19
-
generic
-
generic
-
Not verified
ADDITIONAL SYSTEM INFORMATION :
macOS Big Sur 11.1
java version "1.8.0_291"
Java(TM) SE Runtime Environment (build 1.8.0_291-b10)
Java HotSpot(TM) 64-Bit Server VM (build 25.291-b10, mixed mode)
Also
Mac OS Catalina 10.15.7
java version "1.8.0_161"
Java(TM) SE Runtime Environment (build 1.8.0_161-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.161-b12, mixed mode)
A DESCRIPTION OF THE PROBLEM :
Under some circumstances ObjectInputStream.readObject() will throw a ClassNotFoundException even though the class of the object being read is already loaded.
This happens in the following circumstances:
- S wishes to send R an object O of class C
- C is not in R's class path
- R has access to a user defined class loader that can load C from an external source
- S knows that R does not have C loaded
- S sends R an out-of-band message to load C
- R loads C and acknowledges to S
- S sends R the object
- on attempting to read the object R receives a ClassNotFoundException
The issue arises in the ObjectInputStream.resolveClass() method. This method is called by readObject() as part of the object read process. The resolveClass() method contains the following line:
return Class.forName(name, false, latestUserDefinedLoader());
where latestUserDefinedLoader() seems to be a heuristic for determining the loader most likely to have loaded the class being read.
In the circumstances above the wrong loader is returned causing the Class.forName() call to return the ClassNotFoundException. The loader required is the one R used to load C. What loader is returned by the latestUserDefinedLoader() call is unknown to me.
I have proven this is the problem but subclassing ObjectInputStream and overriding the resolveClass() method as below.
With this code in place the loader that R used to load C is used by resolveClass() and the object is correctly read and returned.
The solution would appear to be to provide an alternative readObject() method that allows the caller to specify the class loader to be used to resolve the class.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
As above
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Object sent by S
ACTUAL -
ClassNotFoundException
---------- BEGIN SOURCE ----------
Too complex - requires sender and receiver code, a codebase, knowledge of the tester's platform and file system, ...
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
----------- Code that bypasses the problem by subclassing ObjectInputStream ----------------
Usage:
- call setDoingOISRead() immediately before attempting to read C, nominating the class loader used to load C
- call clearDoingOISRead() immediately after the read returns
package q.simpleCommon.simpleClasses;
// An override of ObjectInputStream that allows us to specify the class loader to be used to
// resolve a class that is being downloaded via an object input stream
//
// This class is intended to be used to overcome a problem experienced by Java method caller where the classes to
// be used in a method call are preloaded to prevent ClassNotFound errors in downloading the parameter
// objects but the actual download of the request object (which contains the parameter objects) fails because
// ObjectInputStream tries to resolve the downloaded object classes using a specific class loader that
// doesn't know about them rather than the loader we used to preload them.
//
// During the period that doingOISRead the OIS method resolve() will use the loaderToUseForObjectInputStreamRead
// for all class resolves and that method is used for things other than downloads of objects; so
// doingOISRead should be set just ahead of the intended read and cleared just after
//
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
public class APILoaderObjectInputStream extends ObjectInputStream {
public ClassLoader loaderToUseForObjectInputStreamRead = null;
public boolean doingOISRead = false;
public APILoaderObjectInputStream(InputStream inputStream) throws IOException {
super(inputStream);
}
public void setDoingOISRead(ClassLoader loaderToUseForObjectInputStreamRead) {
doingOISRead = true;
this.loaderToUseForObjectInputStreamRead = loaderToUseForObjectInputStreamRead;
}
public void clearDoingOISRead() {
doingOISRead = false;
}
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
// Override the resolve class method and use our own class loader (but only if doingOISRead is
// currently set)
if (!doingOISRead) {
return super.resolveClass(desc);
}
if (loaderToUseForObjectInputStreamRead == null) {
throw new RuntimeException("Loader to use has not been set");
}
String name = desc.getName();
try {
Class<?> result = Class.forName(name, false, loaderToUseForObjectInputStreamRead);
return result;
} catch (ClassNotFoundException e) {
return super.resolveClass(desc); // Will probably give the same result
}
}
}
FREQUENCY : always
macOS Big Sur 11.1
java version "1.8.0_291"
Java(TM) SE Runtime Environment (build 1.8.0_291-b10)
Java HotSpot(TM) 64-Bit Server VM (build 25.291-b10, mixed mode)
Also
Mac OS Catalina 10.15.7
java version "1.8.0_161"
Java(TM) SE Runtime Environment (build 1.8.0_161-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.161-b12, mixed mode)
A DESCRIPTION OF THE PROBLEM :
Under some circumstances ObjectInputStream.readObject() will throw a ClassNotFoundException even though the class of the object being read is already loaded.
This happens in the following circumstances:
- S wishes to send R an object O of class C
- C is not in R's class path
- R has access to a user defined class loader that can load C from an external source
- S knows that R does not have C loaded
- S sends R an out-of-band message to load C
- R loads C and acknowledges to S
- S sends R the object
- on attempting to read the object R receives a ClassNotFoundException
The issue arises in the ObjectInputStream.resolveClass() method. This method is called by readObject() as part of the object read process. The resolveClass() method contains the following line:
return Class.forName(name, false, latestUserDefinedLoader());
where latestUserDefinedLoader() seems to be a heuristic for determining the loader most likely to have loaded the class being read.
In the circumstances above the wrong loader is returned causing the Class.forName() call to return the ClassNotFoundException. The loader required is the one R used to load C. What loader is returned by the latestUserDefinedLoader() call is unknown to me.
I have proven this is the problem but subclassing ObjectInputStream and overriding the resolveClass() method as below.
With this code in place the loader that R used to load C is used by resolveClass() and the object is correctly read and returned.
The solution would appear to be to provide an alternative readObject() method that allows the caller to specify the class loader to be used to resolve the class.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
As above
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Object sent by S
ACTUAL -
ClassNotFoundException
---------- BEGIN SOURCE ----------
Too complex - requires sender and receiver code, a codebase, knowledge of the tester's platform and file system, ...
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
----------- Code that bypasses the problem by subclassing ObjectInputStream ----------------
Usage:
- call setDoingOISRead() immediately before attempting to read C, nominating the class loader used to load C
- call clearDoingOISRead() immediately after the read returns
package q.simpleCommon.simpleClasses;
// An override of ObjectInputStream that allows us to specify the class loader to be used to
// resolve a class that is being downloaded via an object input stream
//
// This class is intended to be used to overcome a problem experienced by Java method caller where the classes to
// be used in a method call are preloaded to prevent ClassNotFound errors in downloading the parameter
// objects but the actual download of the request object (which contains the parameter objects) fails because
// ObjectInputStream tries to resolve the downloaded object classes using a specific class loader that
// doesn't know about them rather than the loader we used to preload them.
//
// During the period that doingOISRead the OIS method resolve() will use the loaderToUseForObjectInputStreamRead
// for all class resolves and that method is used for things other than downloads of objects; so
// doingOISRead should be set just ahead of the intended read and cleared just after
//
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
public class APILoaderObjectInputStream extends ObjectInputStream {
public ClassLoader loaderToUseForObjectInputStreamRead = null;
public boolean doingOISRead = false;
public APILoaderObjectInputStream(InputStream inputStream) throws IOException {
super(inputStream);
}
public void setDoingOISRead(ClassLoader loaderToUseForObjectInputStreamRead) {
doingOISRead = true;
this.loaderToUseForObjectInputStreamRead = loaderToUseForObjectInputStreamRead;
}
public void clearDoingOISRead() {
doingOISRead = false;
}
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
// Override the resolve class method and use our own class loader (but only if doingOISRead is
// currently set)
if (!doingOISRead) {
return super.resolveClass(desc);
}
if (loaderToUseForObjectInputStreamRead == null) {
throw new RuntimeException("Loader to use has not been set");
}
String name = desc.getName();
try {
Class<?> result = Class.forName(name, false, loaderToUseForObjectInputStreamRead);
return result;
} catch (ClassNotFoundException e) {
return super.resolveClass(desc); // Will probably give the same result
}
}
}
FREQUENCY : always