Scanning multi version jars?

Stephen Felts stephen.felts at oracle.com
Thu Sep 14 03:15:01 UTC 2017


We ran into this problem, where we have a closed-set class checker and it has a problem processing MR jar files.

I recommend replacing all inner classes if the ordinary class is versioned.  If the inner class goes away, you would need to stub it so a versioned copy exists.  That is the convention we have adopted for our project but  that is not currently a universal convention (and I believe that we saw one MR jar that didn't follow this convention - maybe the new JAXB jar?).

If you use the JDK9 API to scan classes in a jar file for version 9, you will get META-INF/versions/9/org/example/Foo.class  and org/example/Foo$Bar.class. 

This is the code that we use to read the classes.

>         // the JDK9 mode.  This is accomplished by supplying the "base version" to the constructor.
>         Method baseVersionMethod = null;
>         try {
>           baseVersionMethod = JarFile.class.getMethod("baseVersion");
>         } catch (NoSuchMethodException nsme) {}
>
>         Object baseVersion = null;
>         if (baseVersionMethod != null) {
>           try {
>             baseVersion = baseVersionMethod.invoke(null);
>           } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
>             throw new RuntimeException(e);
>           }
>         }
>
>         Constructor<?> jarFileConstructor = null;
>         if (baseVersion != null) {
>           try {
>             jarFileConstructor = JarFile.class.getConstructor(File.class, boolean.class, int.class, baseVersion.getClass());
>           } catch (NoSuchMethodException | SecurityException e) {
>             throw new RuntimeException(e);
>           }
>         }
>
>         JarFile jarFile;
>         if (jarFileConstructor != null) {
>           try {
>             jarFile = (JarFile) jarFileConstructor.newInstance(new File(filePath), Boolean.TRUE, ZipFile.OPEN_READ, baseVersion);
>           } catch (InvocationTargetException i) {
>             Throwable t = i.getCause();
>             if (t instanceof IOException)
>               throw (IOException) t;
>             throw new RuntimeException(t);
>           } catch (InstantiationException | IllegalAccessException | IllegalArgumentException e) {
>             throw new RuntimeException(e);
>           }
>         } else {
>           jarFile = new JarFile(filePath);
>         }

In our case when processing a MR jar, we assume that if a class is versioned, we will ignore all related classes (ordinary and inner).
We need to keep a list of all classes and not process any of them until we get the full list.
That is, we post process the class list so that we drop ordinary classes and inner classes if there is versioned replacement of any of them.
We avoid false failures by taking this approach (we ignore inner classes for older releases that reference a class that might no longer exist).

We know that this is not purely correct.  It's possible that the versioned replacement class references the non-versioned inner class. 
I think that the correct way would be to see if the inner class is referenced by any class file but that isn't doable so you will need to choose a heuristic.

In the case of annotation processing, it seems the heuristic should be to include the non-versioned inner class since it could be referenced by another class somewhere. That should be simple since you don't need to wait to process files.




-----Original Message-----
From: Greg Wilkins [mailto:gregw at webtide.com] 
Sent: Wednesday, September 13, 2017 6:12 PM
To: jigsaw-dev at openjdk.java.net
Subject: Scanning multi version jars?

I hope this is the right group for this question. please redirect me if not.

The Jetty project is trying to implement annotation scanning for multi version jars and have some concerns with some edge cases, specifically with inner classes.

A multi versioned jar might contain something like:

   - org/example/Foo.class
   - org/example/Foo$Bar.class
   - META-INF/versions/9/org/example/Foo.class

It is clear that there is a java 9 version of Foo.  But what is unclear is the inner class Foo$Bar?  Is that only used by the base Foo version? or does the java 9 version also use the Foo$Bar inner class, but it didn't use any java 9 features, so the base version is able to be used??

It is unclear from just an index of the jar if we should be scanning Foo$Bar for annotations.  So currently it appears that we have to actually scan the Foo class to see if Foo$Bar is referenced and only then scan Foo$Bar for annotations (and recursive analysis for any Foo$Bar$Bob class )!

An alternative would be if there was an enforced convention that any versioned class would also version all of it's inner classes - which may be a reasonable assumption given that they would be compiled together, but we see nothing in the specifications that force a jar to be assembled that way.

Any guidance anybody can give would be helpful.

cheers
Greg Wilkins

PS. The JarFile.getEntry method does not appear to respect it's javadoc with respect to multiversioned jars: it says it will do a search for the most recent version, however the code indicates that the search is only done if the base version does not exist.  This is kind of separate issue, but makes it difficult to defer the behaviour of what to scan to the implementation in JarFile






--
Greg Wilkins <gregw at webtide.com> CTO http://webtide.com


More information about the jigsaw-dev mailing list