8152733: Avoid creating Manifest when checking for Multi-Release attribute

Co-authored-by: Steve Drach <steve.drach@oracle.com>
Reviewed-by: psandoz, alanb
This commit is contained in:
Claes Redestad 2016-03-28 22:25:32 +02:00
parent 63cdcb2f4b
commit 996686aae0
2 changed files with 113 additions and 86 deletions

View File

@ -28,7 +28,6 @@ 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;
@ -38,11 +37,10 @@ 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;
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}.
@ -144,8 +142,9 @@ class JarFile extends ZipFile {
private final int version;
private boolean notVersioned;
private final boolean runtimeVersioned;
private boolean isMultiRelease; // is jar multi-release?
// indicates if Class-Path attribute present (only valid if hasCheckedSpecialAttributes true)
// indicates if Class-Path attribute present
private boolean hasClassPathAttribute;
// true if manifest checked for special attributes
private volatile boolean hasCheckedSpecialAttributes;
@ -155,24 +154,18 @@ class JarFile extends ZipFile {
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) {
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;
@ -353,8 +346,14 @@ class JarFile extends ZipFile {
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();
if (MULTI_RELEASE_FORCED) {
this.version = RUNTIME_VERSION;
version = Release.RUNTIME;
} else {
this.version = version.value();
}
this.runtimeVersioned = version == Release.RUNTIME;
assert runtimeVersionExists();
}
@ -392,35 +391,18 @@ class JarFile extends ZipFile {
* @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;
if (isMultiRelease) {
return true;
}
if (MULTI_RELEASE_ENABLED && version != BASE_VERSION) {
try {
checkForSpecialAttributes();
} catch (IOException io) {
isMultiRelease = false;
}
}
return isMultiRelease;
}
// 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.
@ -905,26 +887,44 @@ class JarFile extends ZipFile {
}
// 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;
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 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;
CLASSPATH_LASTOCC = new byte[64];
CLASSPATH_LASTOCC[(int)'C' - 32] = 1;
CLASSPATH_LASTOCC[(int)'L' - 32] = 2;
CLASSPATH_LASTOCC[(int)'S' - 32] = 5;
CLASSPATH_LASTOCC[(int)'-' - 32] = 6;
CLASSPATH_LASTOCC[(int)'P' - 32] = 7;
CLASSPATH_LASTOCC[(int)'A' - 32] = 8;
CLASSPATH_LASTOCC[(int)'T' - 32] = 9;
CLASSPATH_LASTOCC[(int)'H' - 32] = 10;
CLASSPATH_LASTOCC[(int)':' - 32] = 11;
CLASSPATH_LASTOCC[(int)' ' - 32] = 12;
MULTIRELEASE_LASTOCC = new byte[64];
MULTIRELEASE_LASTOCC[(int)'M' - 32] = 1;
MULTIRELEASE_LASTOCC[(int)'U' - 32] = 2;
MULTIRELEASE_LASTOCC[(int)'T' - 32] = 4;
MULTIRELEASE_LASTOCC[(int)'I' - 32] = 5;
MULTIRELEASE_LASTOCC[(int)'-' - 32] = 6;
MULTIRELEASE_LASTOCC[(int)'R' - 32] = 7;
MULTIRELEASE_LASTOCC[(int)'L' - 32] = 9;
MULTIRELEASE_LASTOCC[(int)'A' - 32] = 11;
MULTIRELEASE_LASTOCC[(int)'S' - 32] = 12;
MULTIRELEASE_LASTOCC[(int)'E' - 32] = 13;
MULTIRELEASE_LASTOCC[(int)':' - 32] = 14;
MULTIRELEASE_LASTOCC[(int)' ' - 32] = 15;
}
private JarEntry getManEntry() {
@ -962,22 +962,33 @@ class JarFile extends ZipFile {
/**
* 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.
* The {@code lastOcc} array is the precomputed bad character shifts.
* Since there are no repeated substring in our search strings,
* the good suffix shifts can be replaced with a comparison.
*/
private boolean match(char[] src, byte[] b, int[] lastOcc, int[] optoSft) {
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--) {
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]);
while (i <= last) {
for (int j = (len - 1); j >= 0; j--) {
byte c = b[i + j];
if (c >= ' ' && c <= 'z') {
if (c >= 'a') c -= 32; // Canonicalize
if (c != src[j]) {
// no match
int goodShift = (j < len - 1) ? len : 1;
int badShift = lastOcc[c - 32];
i += Math.max(j + 1 - badShift, goodShift);
continue next;
}
} else {
// no match, character not valid for name
i += len;
continue next;
}
}
}
return true;
}
@ -986,17 +997,29 @@ class JarFile extends ZipFile {
/**
* On first invocation, check if the JAR file has the Class-Path
* attribute. A no-op on subsequent calls.
* and the Multi-Release 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;
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;
}
hasCheckedSpecialAttributes = true;
}
private synchronized void ensureInitialization() {

View File

@ -83,6 +83,10 @@ public class MultiReleaseJarAPI {
}
try (JarFile jf = new JarFile(multirelease)) {
Assert.assertFalse(jf.isMultiRelease());
}
try (JarFile jf = new JarFile(multirelease, true, ZipFile.OPEN_READ, Release.RUNTIME)) {
Assert.assertTrue(jf.isMultiRelease());
}
}