< prev index next >
src/java.base/share/classes/java/util/jar/JarFile.java
Print this page
rev 13960 : 8152733: Avoid creating Manifest when checking for Multi-Release attribute
Reviewed-by: psandoz, sdrach, dchuyko
Contributed-by: claes.redestad@oracle.com, steve.drach@oracle.com
*** 26,50 ****
package java.util.jar;
import java.io.*;
import java.lang.ref.SoftReference;
import java.net.URL;
- import java.security.PrivilegedAction;
import java.util.*;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import java.util.zip.*;
import java.security.CodeSigner;
import java.security.cert.Certificate;
import java.security.AccessController;
import java.security.CodeSource;
import jdk.internal.misc.SharedSecrets;
import sun.security.util.ManifestEntryVerifier;
import sun.security.util.SignatureFileVerifier;
- import static java.util.jar.Attributes.Name.MULTI_RELEASE;
-
/**
* The {@code JarFile} class is used to read the contents of a jar file
* from any file that can be opened with {@code java.io.RandomAccessFile}.
* It extends the class {@code java.util.zip.ZipFile} with support
* for reading an optional {@code Manifest} entry, and support for
--- 26,48 ----
package java.util.jar;
import java.io.*;
import java.lang.ref.SoftReference;
import java.net.URL;
import java.util.*;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import java.util.zip.*;
import java.security.CodeSigner;
import java.security.cert.Certificate;
import java.security.AccessController;
import java.security.CodeSource;
import jdk.internal.misc.SharedSecrets;
+ import sun.security.action.GetPropertyAction;
import sun.security.util.ManifestEntryVerifier;
import sun.security.util.SignatureFileVerifier;
/**
* The {@code JarFile} class is used to read the contents of a jar file
* from any file that can be opened with {@code java.io.RandomAccessFile}.
* It extends the class {@code java.util.zip.ZipFile} with support
* for reading an optional {@code Manifest} entry, and support for
*** 142,180 ****
private boolean jvInitialized;
private boolean verify;
private final int version;
private boolean notVersioned;
private final boolean runtimeVersioned;
! // indicates if Class-Path attribute present (only valid if hasCheckedSpecialAttributes true)
private boolean hasClassPathAttribute;
// true if manifest checked for special attributes
private volatile boolean hasCheckedSpecialAttributes;
static {
// Set up JavaUtilJarAccess in SharedSecrets
SharedSecrets.setJavaUtilJarAccess(new JavaUtilJarAccessImpl());
BASE_VERSION = 8; // one less than lowest version for versioned entries
! RUNTIME_VERSION = AccessController.doPrivileged(
! new PrivilegedAction<Integer>() {
! public Integer run() {
! Integer v = jdk.Version.current().major();
! Integer i = Integer.getInteger("jdk.util.jar.version", v);
! i = i < 0 ? 0 : i;
! return i > v ? v : i;
! }
! }
! );
! String multi_release = AccessController.doPrivileged(
! new PrivilegedAction<String>() {
! public String run() {
! return System.getProperty("jdk.util.jar.enableMultiRelease", "true");
! }
! }
! );
! switch (multi_release) {
case "true":
default:
MULTI_RELEASE_ENABLED = true;
MULTI_RELEASE_FORCED = false;
break;
--- 140,173 ----
private boolean jvInitialized;
private boolean verify;
private final int version;
private boolean notVersioned;
private final boolean runtimeVersioned;
+ private boolean isMultiRelease; // is jar multi-release?
! // indicates if Class-Path attribute present
private boolean hasClassPathAttribute;
// true if manifest checked for special attributes
private volatile boolean hasCheckedSpecialAttributes;
static {
// Set up JavaUtilJarAccess in SharedSecrets
SharedSecrets.setJavaUtilJarAccess(new JavaUtilJarAccessImpl());
BASE_VERSION = 8; // one less than lowest version for versioned entries
! int runtimeVersion = jdk.Version.current().major();
! String jarVersion = AccessController.doPrivileged(
! new GetPropertyAction("jdk.util.jar.version"));
! if (jarVersion != null) {
! int jarVer = Integer.parseInt(jarVersion);
! runtimeVersion = (jarVer > runtimeVersion)
! ? runtimeVersion : Math.max(jarVer, 0);
! }
! RUNTIME_VERSION = runtimeVersion;
! String enableMultiRelease = AccessController.doPrivileged(
! new GetPropertyAction("jdk.util.jar.enableMultiRelease", "true"));
! switch (enableMultiRelease) {
case "true":
default:
MULTI_RELEASE_ENABLED = true;
MULTI_RELEASE_FORCED = false;
break;
*** 351,362 ****
public JarFile(File file, boolean verify, int mode, Release version) throws IOException {
super(file, mode);
Objects.requireNonNull(version);
this.verify = verify;
// version applies to multi-release jar files, ignored for regular jar files
! this.version = MULTI_RELEASE_FORCED ? RUNTIME_VERSION : version.value();
this.runtimeVersioned = version == Release.RUNTIME;
assert runtimeVersionExists();
}
private boolean runtimeVersionExists() {
int version = jdk.Version.current().major();
--- 344,361 ----
public JarFile(File file, boolean verify, int mode, Release version) throws IOException {
super(file, mode);
Objects.requireNonNull(version);
this.verify = verify;
// version applies to multi-release jar files, ignored for regular jar files
! if (MULTI_RELEASE_FORCED) {
! this.version = RUNTIME_VERSION;
! version = Release.RUNTIME;
! } else {
! this.version = version.value();
! }
this.runtimeVersioned = version == Release.RUNTIME;
+
assert runtimeVersionExists();
}
private boolean runtimeVersionExists() {
int version = jdk.Version.current().major();
*** 390,428 ****
*
* @return true if this JarFile is a multi-release jar file
* @since 9
*/
public final boolean isMultiRelease() {
! // do not call this code in a constructor because some subclasses use
! // lazy loading of manifest so it won't be available at construction time
! if (MULTI_RELEASE_ENABLED) {
! // Doubled-checked locking pattern
! Boolean result = isMultiRelease;
! if (result == null) {
! synchronized (this) {
! result = isMultiRelease;
! if (result == null) {
! Manifest man = null;
! try {
! man = getManifest();
! } catch (IOException e) {
! //Ignored, manifest cannot be read
! }
! isMultiRelease = result = (man != null)
! && man.getMainAttributes().containsKey(MULTI_RELEASE)
! ? Boolean.TRUE : Boolean.FALSE;
! }
}
}
- return result == Boolean.TRUE;
- } else {
- return false;
}
}
- // the following field, isMultiRelease, should only be used in the method
- // isMultiRelease(), like a static local
- private volatile Boolean isMultiRelease; // is jar multi-release?
/**
* Returns the jar file manifest, or {@code null} if none.
*
* @return the jar file manifest, or {@code null} if none
--- 389,410 ----
*
* @return true if this JarFile is a multi-release jar file
* @since 9
*/
public final boolean isMultiRelease() {
! if (isMultiRelease) {
! return true;
}
+ if (MULTI_RELEASE_ENABLED && version != BASE_VERSION) {
+ try {
+ checkForSpecialAttributes();
+ } catch (IOException io) {
+ isMultiRelease = false;
}
}
+ return isMultiRelease;
}
/**
* Returns the jar file manifest, or {@code null} if none.
*
* @return the jar file manifest, or {@code null} if none
*** 903,932 ****
}
return (JarEntry)ze;
}
// Statics for hand-coded Boyer-Moore search
! private static final char[] CLASSPATH_CHARS = {'c','l','a','s','s','-','p','a','t','h'};
! // The bad character shift for "class-path"
! private static final int[] CLASSPATH_LASTOCC;
! // The good suffix shift for "class-path"
! private static final int[] CLASSPATH_OPTOSFT;
static {
! CLASSPATH_LASTOCC = new int[128];
! CLASSPATH_OPTOSFT = new int[10];
CLASSPATH_LASTOCC[(int)'c'] = 1;
CLASSPATH_LASTOCC[(int)'l'] = 2;
CLASSPATH_LASTOCC[(int)'s'] = 5;
CLASSPATH_LASTOCC[(int)'-'] = 6;
CLASSPATH_LASTOCC[(int)'p'] = 7;
CLASSPATH_LASTOCC[(int)'a'] = 8;
CLASSPATH_LASTOCC[(int)'t'] = 9;
CLASSPATH_LASTOCC[(int)'h'] = 10;
! for (int i=0; i<9; i++)
! CLASSPATH_OPTOSFT[i] = 10;
! CLASSPATH_OPTOSFT[9]=1;
}
private JarEntry getManEntry() {
if (manEntry == null) {
// First look up manifest entry using standard name
--- 885,928 ----
}
return (JarEntry)ze;
}
// Statics for hand-coded Boyer-Moore search
! private static final byte[] CLASSPATH_CHARS = {'c','l','a','s','s','-','p','a','t','h', ':', ' '};
! // The bad character shift for "class-path:"
! private static final byte[] CLASSPATH_LASTOCC;
!
! private static final byte[] MULTIRELEASE_CHARS = {'m','u','l','t','i','-','r','e','l','e', 'a', 's', 'e', ':', ' '};
! // The bad character shift for "multi-release: "
! private static final byte[] MULTIRELEASE_LASTOCC;
static {
! CLASSPATH_LASTOCC = new byte[128];
CLASSPATH_LASTOCC[(int)'c'] = 1;
CLASSPATH_LASTOCC[(int)'l'] = 2;
CLASSPATH_LASTOCC[(int)'s'] = 5;
CLASSPATH_LASTOCC[(int)'-'] = 6;
CLASSPATH_LASTOCC[(int)'p'] = 7;
CLASSPATH_LASTOCC[(int)'a'] = 8;
CLASSPATH_LASTOCC[(int)'t'] = 9;
CLASSPATH_LASTOCC[(int)'h'] = 10;
! CLASSPATH_LASTOCC[(int)':'] = 11;
! CLASSPATH_LASTOCC[(int)' '] = 12;
!
! MULTIRELEASE_LASTOCC = new byte[128];
! MULTIRELEASE_LASTOCC[(int)'m'] = 1;
! MULTIRELEASE_LASTOCC[(int)'u'] = 2;
! MULTIRELEASE_LASTOCC[(int)'t'] = 4;
! MULTIRELEASE_LASTOCC[(int)'i'] = 5;
! MULTIRELEASE_LASTOCC[(int)'-'] = 6;
! MULTIRELEASE_LASTOCC[(int)'r'] = 7;
! MULTIRELEASE_LASTOCC[(int)'l'] = 9;
! MULTIRELEASE_LASTOCC[(int)'a'] = 11;
! MULTIRELEASE_LASTOCC[(int)'s'] = 12;
! MULTIRELEASE_LASTOCC[(int)'e'] = 13;
! MULTIRELEASE_LASTOCC[(int)':'] = 14;
! MULTIRELEASE_LASTOCC[(int)' '] = 15;
}
private JarEntry getManEntry() {
if (manEntry == null) {
// First look up manifest entry using standard name
*** 960,1005 ****
return hasClassPathAttribute;
}
/**
* Returns true if the pattern {@code src} is found in {@code b}.
! * The {@code lastOcc} and {@code optoSft} arrays are the precomputed
! * bad character and good suffix shifts.
*/
! private boolean match(char[] src, byte[] b, int[] lastOcc, int[] optoSft) {
int len = src.length;
int last = b.length - len;
int i = 0;
next:
! while (i<=last) {
! for (int j=(len-1); j>=0; j--) {
! char c = (char) b[i+j];
! c = (((c-'A')|('Z'-c)) >= 0) ? (char)(c + 32) : c;
if (c != src[j]) {
! i += Math.max(j + 1 - lastOcc[c&0x7F], optoSft[j]);
continue next;
}
}
return true;
}
return false;
}
/**
* On first invocation, check if the JAR file has the Class-Path
! * attribute. A no-op on subsequent calls.
*/
private void checkForSpecialAttributes() throws IOException {
! if (hasCheckedSpecialAttributes) return;
JarEntry manEntry = getManEntry();
if (manEntry != null) {
byte[] b = getBytes(manEntry);
! if (match(CLASSPATH_CHARS, b, CLASSPATH_LASTOCC, CLASSPATH_OPTOSFT))
! hasClassPathAttribute = true;
}
hasCheckedSpecialAttributes = true;
}
private synchronized void ensureInitialization() {
try {
maybeInstantiateVerifier();
} catch (IOException e) {
--- 956,1014 ----
return hasClassPathAttribute;
}
/**
* Returns true if the pattern {@code src} is found in {@code b}.
! * The {@code lastOcc} array is the precomputed bad character shifts.
*/
! private boolean match(byte[] src, byte[] b, byte[] lastOcc) {
int len = src.length;
int last = b.length - len;
int i = 0;
next:
! while (i <= last) {
! for (int j = (len - 1); j >= 0; j--) {
! byte c = b[i + j];
! if (c >= 'A' && c <= 'Z') {
! c += 32;
! }
if (c != src[j]) {
! i += Math.max(j + 1 - lastOcc[c&0x7F], j < len - 1 ? len : 1);
continue next;
}
}
return true;
}
return false;
}
/**
* On first invocation, check if the JAR file has the Class-Path
! * and the Multi-Release attribute. A no-op on subsequent calls.
*/
private void checkForSpecialAttributes() throws IOException {
! if (hasCheckedSpecialAttributes) {
! return;
! }
! synchronized (this) {
! if (hasCheckedSpecialAttributes) {
! return;
! }
JarEntry manEntry = getManEntry();
if (manEntry != null) {
byte[] b = getBytes(manEntry);
! hasClassPathAttribute = match(CLASSPATH_CHARS, b,
! CLASSPATH_LASTOCC);
! // is this a multi-release jar file
! if (MULTI_RELEASE_ENABLED && version != BASE_VERSION) {
! isMultiRelease = match(MULTIRELEASE_CHARS, b,
! MULTIRELEASE_LASTOCC);
! }
}
hasCheckedSpecialAttributes = true;
}
+ }
private synchronized void ensureInitialization() {
try {
maybeInstantiateVerifier();
} catch (IOException e) {
< prev index next >