javax.jnlp.PersistenceService bug: ArrayIndexOutOfBoundsException
com.sun.jnlp.PersistenceServiceImpl, which is Sun standard implementation of PersistenceService interface, only allows for 255 storage entities for a given codebase.
After some decompiling I found out it keeps an internal array, size 255. Very very dumb move IMHO.
If you try to register a 256th entity, it fails horribly with a ArrayIndexOutOfBoundsException. This even if all the entries are 0-length, and the storage if effectively using no space at all.
This effectively hinders any usage of this API as a 'sandbox friendly' filesystem, as I was trying to do, by making the database for my JWS Application save to the PersistentService storage. This is because any 'serious' application creates many files for data and metadata (logs, etc...), and this make PersistenceService completely unfit for the task.
Moreover this limit is unneeded for security reasons, since size limits are already in, and it would be easily fixed by replacing the fixed size array with an ArrayList, with no consequences at all on the performance or features, except giving user a more capable PersistenceService implementation, that works as intended by the API contract, instead of firing unforeseen exceptions.
I've filed a bug with bugs.sun.com, let's see if they can fix this one.
What do you think about this?
[1346 byte] By [
omeroa] at [2007-11-26 22:40:55]

# 2
I've tested it on latest final JDK6 for windows, and on latest JDK6 for Mac OS X (which is based on beta88).
Anyway, this is the same for every single jdk in the last few years, since the bugged class (com.sun.jnlp.PersistenceServiceImpl) hasn't changed for quite a while AFAIK.
By decompiling the class with JAD, I can find this
"private static class MuffinAccessVisitor implements DiskCacheVisitor" with the field "private URL _urls[]" which is initialized this way in constructor "_urls = new URL[255];".
This bug is very easily tested with this code anyway. This code fails on any JDK I've tried with the same error: "ArrayIndexOutOfBoundsException: 255".
import java.net.URL;
import java.util.Random;
import javax.jnlp.BasicService;
import javax.jnlp.PersistenceService;
import javax.jnlp.ServiceManager;
import javax.swing.JFrame;
import javax.swing.JTextField;
public class JNLPTester {
private static PersistenceService persistenceService;
private static BasicService basicService;
private static URL codebase;
static {
try {
// Get PersistenceService for operations, BasicService for codebase
persistenceService = (PersistenceService) ServiceManager.lookup("javax.jnlp.PersistenceService");
basicService = (BasicService) ServiceManager.lookup("javax.jnlp.BasicService");
codebase = basicService.getCodeBase();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
int counter = 0;
try {
for (String url : persistenceService.getNames(codebase)) {
persistenceService.delete(new URL(codebase, url));
}
Random random = new Random();
while (true) {
// Loop and create a new storage entity with a random name
URL url = new URL(codebase, "" + random.nextLong());
persistenceService.create(url, 0L); // zero-length
counter++;
}
} catch (Exception e) {
JFrame frame = new JFrame();
frame.add(new JTextField("Counter: " + counter + " - Exception: " + e + " - Message: " + e.getMessage() ));
frame.setVisible(true);
}
}
}
omeroa at 2007-7-10 11:55:10 >

# 3
I've downloaded official latest JDK6 sources from sun (jdk-6u1-ea-src-b03-jrl-19_jan_2007.jar), and I've had a look at the actual sources.
com.sun.jnlp.PersistenceServiceImpl depends on com.sun.deploy.Cache static methods for operations: it's this latter class that handles the persistence entities creation/removal, etc... (they are named 'muffins' in the implementation).
This is PersistenceServiceImpl.create
public long create(final URL url, final long maxsize)
throws MalformedURLException, IOException {
// compute new limit based on this maxsize + maxsize of all other
// entries this application has access to. If this total is
// >= _limit then ask user if they want to increase the _limit
checkAccess(url);
Long l = null;
long newmaxsize = -1;
if ((newmaxsize = checkSetMaxSize(url, maxsize)) < 0) return -1;
final long pass_newmaxsize = newmaxsize;
try {
l = (Long)AccessController.doPrivileged(
new PrivilegedExceptionAction() {
public Object run() throws MalformedURLException, IOException {
Cache.createMuffinEntry(url, PersistenceService.CACHED, pass_newmaxsize);
return new Long(pass_newmaxsize);
}
});
} catch (PrivilegedActionException e) {
Exception ee = e.getException();
if (ee instanceof IOException) {
throw (IOException)ee;
} else if (ee instanceof MalformedURLException) {
throw (MalformedURLException)ee;
}
}
return l.longValue();
}
As you can see the method calls "checkSetMaxSize"
private long checkSetMaxSize(final URL url, final long maxsize)
throws IOException {
// algorithm:
// friendTotalMaxSize = sum of maxsize of all other muffins accessible
// by the host that made this muffin. (i.e. muffins with same
// host as this one)
// if friendTotalMaxSize + maxsize > application maxsize, ask user if
// they want to increase application maxsize.
URL [] friendMuffins = null;
try {
friendMuffins = (URL []) AccessController.doPrivileged(
new PrivilegedExceptionAction() {
public Object run() throws IOException{
return (Cache.getAccessibleMuffins(url));
}
});
} catch (PrivilegedActionException e) {
throw (IOException) e.getException();
}
long friendMuffinsTotalMaxSize = 0;
if (friendMuffins != null) {
for (int i = 0; i < friendMuffins.length; i++) {
if (friendMuffins[i] != null) {
final URL friendMuffin = friendMuffins[i];
Long longFriendMuffinsSize = null;
try {
longFriendMuffinsSize =
(Long) AccessController.doPrivileged(
new PrivilegedExceptionAction() {
public Object run() throws IOException{
return new Long(Cache.getMuffinSize(
friendMuffin));
}
});
} catch (PrivilegedActionException e) {
throw (IOException) e.getException();
}
friendMuffinsTotalMaxSize +=
longFriendMuffinsSize.longValue();
}
}
}
Which in turns calls com.sun.deply.Cache.getAccessibleMuffins
public static URL[] getAccessibleMuffins(URL url) throws IOException {
URL[] urls = new URL[255];
// Get the list of files that match this name
final File[] files = muffinDir.listFiles(new FileFilter() {
public boolean accept(File pathname) {
String filename = pathname.getName();
return filename.endsWith(MUFFIN_FILE_EXT);
}
});
URL u;
int urlCount = 0;
for (int i = 0; i < files.length; i++) {
u = getCachedMuffinURL(files[i]);
if (u.getHost().equals(url.getHost())) {
urls[urlCount] = u;
urlCount += 1;
}
}
return urls;
}
Notice the: URL[] urls = new URL[255];
In this class we can find a fixed size array, size 255, which cause the bug.
You can easily test it with the provided test case in my previous reply. :)
omeroa at 2007-7-10 11:55:10 >
