Merge "Bring the latest Caliper (r102) into vogar." into dalvik-dev
diff --git a/libcore/archive/src/main/java/java/util/zip/InflaterInputStream.java b/libcore/archive/src/main/java/java/util/zip/InflaterInputStream.java
index d239a8a..8cd8cf2 100644
--- a/libcore/archive/src/main/java/java/util/zip/InflaterInputStream.java
+++ b/libcore/archive/src/main/java/java/util/zip/InflaterInputStream.java
@@ -279,8 +279,7 @@
* @return 0 if no further bytes are available. Otherwise returns 1,
* which suggests (but does not guarantee) that additional bytes are
* available.
- * @throws IOException
- * If an error occurs.
+ * @throws IOException if this stream is closed or an error occurs
*/
@Override
public int available() throws IOException {
diff --git a/libcore/archive/src/main/java/java/util/zip/ZipInputStream.java b/libcore/archive/src/main/java/java/util/zip/ZipInputStream.java
index c2af30c..0d55a10 100644
--- a/libcore/archive/src/main/java/java/util/zip/ZipInputStream.java
+++ b/libcore/archive/src/main/java/java/util/zip/ZipInputStream.java
@@ -383,18 +383,12 @@
return skipped;
}
- /**
- * Returns 0 if the {@code EOF} has been reached, otherwise returns 1.
- *
- * @return 0 after {@code EOF} of current entry, 1 otherwise.
- * @throws IOException
- * if an IOException occurs.
- */
@Override
public int available() throws IOException {
if (closed) {
throw new IOException(Messages.getString("archive.1E")); //$NON-NLS-1$
}
+ // The InflaterInputStream contract says we must only return 0 or 1.
return (currentEntry == null || inRead < currentEntry.size) ? 1 : 0;
}
diff --git a/libcore/crypto/src/main/java/javax/crypto/CipherInputStream.java b/libcore/crypto/src/main/java/javax/crypto/CipherInputStream.java
index b2c626d..fb1ef27 100644
--- a/libcore/crypto/src/main/java/javax/crypto/CipherInputStream.java
+++ b/libcore/crypto/src/main/java/javax/crypto/CipherInputStream.java
@@ -185,13 +185,6 @@
return i;
}
- /**
- * Returns the number of bytes available without blocking.
- *
- * @return the number of bytes available, currently zero.
- * @throws IOException
- * if an error occurs
- */
@Override
public int available() throws IOException {
return 0;
@@ -227,4 +220,3 @@
return false;
}
}
-
diff --git a/libcore/luni/src/main/java/java/io/BufferedInputStream.java b/libcore/luni/src/main/java/java/io/BufferedInputStream.java
index 1ecae30..369e0d0 100644
--- a/libcore/luni/src/main/java/java/io/BufferedInputStream.java
+++ b/libcore/luni/src/main/java/java/io/BufferedInputStream.java
@@ -94,13 +94,13 @@
}
/**
- * Returns the number of bytes that are available before this stream will
- * block. This method returns the number of bytes available in the buffer
- * plus those available in the source stream.
+ * Returns an estimated number of bytes that can be read or skipped without blocking for more
+ * input. This method returns the number of bytes available in the buffer
+ * plus those available in the source stream, but see {@link InputStream#available} for
+ * important caveats.
*
- * @return the number of bytes available before blocking.
- * @throws IOException
- * if this stream is closed.
+ * @return the estimated number of bytes available
+ * @throws IOException if this stream is closed or an error occurs
*/
@Override
public synchronized int available() throws IOException {
diff --git a/libcore/luni/src/main/java/java/io/ByteArrayInputStream.java b/libcore/luni/src/main/java/java/io/ByteArrayInputStream.java
index f0b7219..9b4b32f 100644
--- a/libcore/luni/src/main/java/java/io/ByteArrayInputStream.java
+++ b/libcore/luni/src/main/java/java/io/ByteArrayInputStream.java
@@ -85,11 +85,9 @@
}
/**
- * Returns the number of bytes that are available before this stream will
- * block. This method returns the number of bytes yet to be read from the
- * source byte array.
+ * Returns the number of remaining bytes.
*
- * @return the number of bytes available before blocking.
+ * @return {@code count - pos}
*/
@Override
public synchronized int available() {
diff --git a/libcore/luni/src/main/java/java/io/FileInputStream.java b/libcore/luni/src/main/java/java/io/FileInputStream.java
index 6a65395..e8aed58 100644
--- a/libcore/luni/src/main/java/java/io/FileInputStream.java
+++ b/libcore/luni/src/main/java/java/io/FileInputStream.java
@@ -134,15 +134,6 @@
this(null == fileName ? (File) null : new File(fileName));
}
- /**
- * Returns the number of bytes that are available before this stream will
- * block. This method always returns the size of the file minus the current
- * position.
- *
- * @return the number of bytes available before blocking.
- * @throws IOException
- * if an error occurs in this stream.
- */
@Override
public int available() throws IOException {
openCheck();
diff --git a/libcore/luni/src/main/java/java/io/FilterInputStream.java b/libcore/luni/src/main/java/java/io/FilterInputStream.java
index 0baa409..9891cdb 100644
--- a/libcore/luni/src/main/java/java/io/FilterInputStream.java
+++ b/libcore/luni/src/main/java/java/io/FilterInputStream.java
@@ -46,14 +46,6 @@
this.in = in;
}
- /**
- * Returns the number of bytes that are available before this stream will
- * block.
- *
- * @return the number of bytes available before blocking.
- * @throws IOException
- * if an error occurs in this stream.
- */
@Override
public int available() throws IOException {
return in.available();
diff --git a/libcore/luni/src/main/java/java/io/InputStream.java b/libcore/luni/src/main/java/java/io/InputStream.java
index 33a5cfd..b16c5a2 100644
--- a/libcore/luni/src/main/java/java/io/InputStream.java
+++ b/libcore/luni/src/main/java/java/io/InputStream.java
@@ -48,13 +48,41 @@
}
/**
- * Returns the number of bytes that are available before this stream will
- * block. This implementation always returns 0. Subclasses should override
- * and indicate the correct number of bytes available.
+ * Returns an estimated number of bytes that can be read or skipped without blocking for more
+ * input.
*
- * @return the number of bytes available before blocking.
- * @throws IOException
- * if an error occurs in this stream.
+ * <p>Note that this method provides such a weak guarantee that it is not very useful in
+ * practice.
+ *
+ * <p>Firstly, the guarantee is "without blocking for more input" rather than "without
+ * blocking": a read may still block waiting for I/O to complete — the guarantee is
+ * merely that it won't have to wait indefinitely for data to be written. The result of this
+ * method should not be used as a license to do I/O on a thread that shouldn't be blocked.
+ *
+ * <p>Secondly, the result is a
+ * conservative estimate and may be significantly smaller than the actual number of bytes
+ * available. In particular, an implementation that always returns 0 would be correct.
+ * In general, callers should only use this method if they'd be satisfied with
+ * treating the result as a boolean yes or no answer to the question "is there definitely
+ * data ready?".
+ *
+ * <p>Thirdly, the fact that a given number of bytes is "available" does not guarantee that a
+ * read or skip will actually read or skip that many bytes: they may read or skip fewer.
+ *
+ * <p>It is particularly important to realize that you <i>must not</i> use this method to
+ * size a container and assume that you can read the entirety of the stream without needing
+ * to resize the container. Such callers should probably write everything they read to a
+ * {@link ByteArrayOutputStream} and convert that to a byte array. Alternatively, if you're
+ * reading from a file, {@link File#length} returns the current length of the file (though
+ * assuming the file's length can't change may be incorrect, reading a file is inherently
+ * racy).
+ *
+ * <p>The default implementation of this method in {@code InputStream} always returns 0.
+ * Subclasses should override this method if they are able to indicate the number of bytes
+ * available.
+ *
+ * @return the estimated number of bytes available
+ * @throws IOException if this stream is closed or an error occurs
*/
public int available() throws IOException {
return 0;
@@ -198,15 +226,16 @@
}
/**
- * Skips at most {@code n} bytes in this stream. It does nothing and returns
- * 0 if {@code n} is negative. Less than {@code n} characters are skipped if
- * the end of this stream is reached before the operation completes.
- * <p>
- * This default implementation reads {@code n} bytes into a temporary
+ * Skips at most {@code n} bytes in this stream. This method does nothing and returns
+ * 0 if {@code n} is negative.
+ *
+ * <p>Note the "at most" in the description of this method: this method may choose to skip
+ * fewer bytes than requested. Callers should <i>always</i> check the return value.
+ *
+ * <p>This default implementation reads bytes into a temporary
* buffer. Concrete subclasses should provide their own implementation.
*
- * @param n
- * the number of bytes to skip.
+ * @param n the number of bytes to skip.
* @return the number of bytes actually skipped.
* @throws IOException
* if this stream is closed or another IOException occurs.
diff --git a/libcore/luni/src/main/java/java/io/LineNumberInputStream.java b/libcore/luni/src/main/java/java/io/LineNumberInputStream.java
index 3df3a05..b8290cf 100644
--- a/libcore/luni/src/main/java/java/io/LineNumberInputStream.java
+++ b/libcore/luni/src/main/java/java/io/LineNumberInputStream.java
@@ -51,17 +51,12 @@
}
/**
- * Returns the number of bytes that are available before this stream will
- * block.
- * <p>
- * Note: The source stream may just be a sequence of {@code "\r\n"} bytes
+ * {@inheritDoc}
+ *
+ * <p>Note that the source stream may just be a sequence of {@code "\r\n"} bytes
* which are converted into {@code '\n'} by this stream. Therefore,
* {@code available} returns only {@code in.available() / 2} bytes as
* result.
- *
- * @return the guaranteed number of bytes available before blocking.
- * @throws IOException
- * if an error occurs in this stream.
*/
@Override
public int available() throws IOException {
diff --git a/libcore/luni/src/main/java/java/io/ObjectInputStream.java b/libcore/luni/src/main/java/java/io/ObjectInputStream.java
index df6d9a2..9d79641a 100644
--- a/libcore/luni/src/main/java/java/io/ObjectInputStream.java
+++ b/libcore/luni/src/main/java/java/io/ObjectInputStream.java
@@ -446,16 +446,6 @@
primitiveData = emptyStream;
}
- /**
- * Returns the number of bytes of primitive data that can be read from this
- * stream without blocking. This method should not be used at any arbitrary
- * position; just when reading primitive data types (int, char etc).
- *
- * @return the number of available primitive data bytes.
- * @throws IOException
- * if any I/O problem occurs while computing the available
- * bytes.
- */
@Override
public int available() throws IOException {
// returns 0 if next data is an object, or N if reading primitive types
diff --git a/libcore/luni/src/main/java/java/io/PipedInputStream.java b/libcore/luni/src/main/java/java/io/PipedInputStream.java
index 507371b..0f4ed0a 100644
--- a/libcore/luni/src/main/java/java/io/PipedInputStream.java
+++ b/libcore/luni/src/main/java/java/io/PipedInputStream.java
@@ -137,13 +137,13 @@
}
/**
- * Returns the number of bytes that are available before this stream will
- * block. This implementation returns the number of bytes written to this
- * pipe that have not been read yet.
+ * {@inheritDoc}
*
- * @return the number of bytes available before blocking.
- * @throws IOException
- * if an error occurs in this stream.
+ * <p>Unlike most streams, {@code PipedInputStream} returns 0 rather than throwing
+ * {@code IOException} if the stream has been closed. Unconnected and broken pipes also
+ * return 0.
+ *
+ * @throws IOException if an I/O error occurs
*/
@Override
public synchronized int available() throws IOException {
diff --git a/libcore/luni/src/main/java/java/io/PushbackInputStream.java b/libcore/luni/src/main/java/java/io/PushbackInputStream.java
index 932a896..5a78eb9 100644
--- a/libcore/luni/src/main/java/java/io/PushbackInputStream.java
+++ b/libcore/luni/src/main/java/java/io/PushbackInputStream.java
@@ -73,16 +73,6 @@
pos = size;
}
- /**
- * Returns the number of bytes that are available before this stream will
- * block. This is the sum of the bytes available in the pushback buffer and
- * those available from the source stream.
- *
- * @return the number of bytes available before blocking.
- * @throws IOException
- * if this stream is closed or an I/O error occurs in the source
- * stream.
- */
@Override
public int available() throws IOException {
if (buf == null) {
diff --git a/libcore/luni/src/main/java/java/io/SequenceInputStream.java b/libcore/luni/src/main/java/java/io/SequenceInputStream.java
index 3719021..c72e521 100644
--- a/libcore/luni/src/main/java/java/io/SequenceInputStream.java
+++ b/libcore/luni/src/main/java/java/io/SequenceInputStream.java
@@ -81,14 +81,6 @@
}
}
- /**
- * Returns the number of bytes that are available before the current input stream will
- * block.
- *
- * @return the number of bytes available in the current input stream before blocking.
- * @throws IOException
- * if an I/O error occurs in the current input stream.
- */
@Override
public int available() throws IOException {
if (e != null && in != null) {
diff --git a/libcore/luni/src/main/java/java/io/StringBufferInputStream.java b/libcore/luni/src/main/java/java/io/StringBufferInputStream.java
index 037cc60..32e89d7 100644
--- a/libcore/luni/src/main/java/java/io/StringBufferInputStream.java
+++ b/libcore/luni/src/main/java/java/io/StringBufferInputStream.java
@@ -60,12 +60,6 @@
count = str.length();
}
- /**
- * Returns the number of bytes that are available before this stream will
- * block.
- *
- * @return the number of bytes available before blocking.
- */
@Override
public synchronized int available() {
return count - pos;
diff --git a/libcore/luni/src/main/java/java/util/ServiceLoader.java b/libcore/luni/src/main/java/java/util/ServiceLoader.java
index 2f28dff..a8e16d5 100644
--- a/libcore/luni/src/main/java/java/util/ServiceLoader.java
+++ b/libcore/luni/src/main/java/java/util/ServiceLoader.java
@@ -20,6 +20,8 @@
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
/**
* A service-provider loader.
@@ -62,6 +64,8 @@
*
* <p>Note that each iteration creates new instances of the various service implementations, so
* any heavily-used code will likely want to cache the known implementations itself and reuse them.
+ * Note also that the candidate classes are instantiated lazily as you call {@code next} on the
+ * iterator: construction of the iterator itself does not instantiate any of the providers.
*
* @param <S> the service class or interface
* @since 1.6
@@ -106,13 +110,17 @@
}
/**
- * Constructs a service loader.
+ * Constructs a service loader. If {@code classLoader} is null, the system class loader
+ * is used.
*
* @param service the service class or interface
- * @param loader the class loader
+ * @param classLoader the class loader
* @return a new ServiceLoader
*/
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader classLoader) {
+ if (classLoader == null) {
+ classLoader = ClassLoader.getSystemClassLoader();
+ }
return new ServiceLoader<S>(service, classLoader);
}
@@ -120,16 +128,12 @@
services.clear();
try {
String name = "META-INF/services/" + service.getName();
- services.addAll(Collections.list(classLoader().getResources(name)));
+ services.addAll(Collections.list(classLoader.getResources(name)));
} catch (IOException e) {
return;
}
}
- private ClassLoader classLoader() {
- return (classLoader != null) ? classLoader : ClassLoader.getSystemClassLoader();
- }
-
/**
* Constructs a service loader, using the current thread's context class loader.
*
@@ -156,6 +160,29 @@
return ServiceLoader.load(service, cl);
}
+ /**
+ * Internal API to support built-in SPIs that check a system property first.
+ * Returns an instance specified by a property with the class' binary name, or null if
+ * no such property is set.
+ * @hide
+ */
+ public static <S> S loadFromSystemProperty(final Class<S> service) {
+ return AccessController.doPrivileged(new PrivilegedAction<S>() {
+ public S run() {
+ try {
+ final String className = System.getProperty(service.getName());
+ if (className != null) {
+ Class<?> c = ClassLoader.getSystemClassLoader().loadClass(className);
+ return (S) c.newInstance();
+ }
+ return null;
+ } catch (Exception e) {
+ throw new Error(e);
+ }
+ }
+ });
+ }
+
@Override
public String toString() {
return "ServiceLoader for " + service.getName();
@@ -190,11 +217,7 @@
}
String className = queue.remove();
try {
- if (classLoader == null) {
- return service.cast(Class.forName(className).newInstance());
- } else {
- return service.cast(classLoader.loadClass(className).newInstance());
- }
+ return service.cast(classLoader.loadClass(className).newInstance());
} catch (Exception e) {
throw new ServiceConfigurationError("Couldn't instantiate class " + className, e);
}
diff --git a/libcore/luni/src/test/java/javax/net/ssl/SSLSocketTest.java b/libcore/luni/src/test/java/javax/net/ssl/SSLSocketTest.java
index c6026d9..0e39d47 100644
--- a/libcore/luni/src/test/java/javax/net/ssl/SSLSocketTest.java
+++ b/libcore/luni/src/test/java/javax/net/ssl/SSLSocketTest.java
@@ -275,6 +275,7 @@
});
thread.start();
final SSLSocket client = (SSLSocket) c.sslContext.getSocketFactory().createSocket(c.host, c.port);
+ final boolean[] handshakeCompletedListenerCalled = new boolean[1];
client.addHandshakeCompletedListener(new HandshakeCompletedListener() {
public void handshakeCompleted(HandshakeCompletedEvent event) {
try {
@@ -320,8 +321,7 @@
} else if (c.keyStore.isKeyEntry(alias)) {
assertNull(key);
key = c.keyStore.getKey(alias, c.keyStorePassword);
- }
- else {
+ } else {
fail();
}
}
@@ -353,6 +353,7 @@
assertNotNull(socket);
assertSame(client, socket);
+ handshakeCompletedListenerCalled[0] = true;
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
@@ -361,6 +362,7 @@
}
});
client.startHandshake();
+ assertTrue(handshakeCompletedListenerCalled[0]);
thread.join();
}
diff --git a/libcore/nio/src/main/java/java/nio/channels/spi/SelectorProvider.java b/libcore/nio/src/main/java/java/nio/channels/spi/SelectorProvider.java
index 7692f2f..ab898af 100644
--- a/libcore/nio/src/main/java/java/nio/channels/spi/SelectorProvider.java
+++ b/libcore/nio/src/main/java/java/nio/channels/spi/SelectorProvider.java
@@ -17,10 +17,7 @@
package java.nio.channels.spi;
-import java.io.BufferedReader;
import java.io.IOException;
-import java.io.InputStreamReader;
-import java.net.URL;
import java.nio.channels.Channel;
import java.nio.channels.DatagramChannel;
import java.nio.channels.Pipe;
@@ -28,8 +25,7 @@
import java.nio.channels.SocketChannel;
import java.security.AccessController;
import java.security.PrivilegedAction;
-import java.util.Enumeration;
-
+import java.util.ServiceLoader;
import org.apache.harmony.luni.platform.Platform;
import org.apache.harmony.nio.internal.SelectorProviderImpl;
@@ -38,19 +34,12 @@
* providing instances of {@link DatagramChannel}, {@link Pipe},
* {@link java.nio.channels.Selector} , {@link ServerSocketChannel}, and
* {@link SocketChannel}. All the methods of this class are thread-safe.
- * <p>
- * A provider instance can be retrieved through a system property or the
- * configuration file in a jar file; if no provide is available that way then
+ *
+ * <p>A provider instance can be retrieved through a system property or the
+ * configuration file in a jar file; if no provider is available that way then
* the system default provider is returned.
*/
-public abstract class SelectorProvider extends Object {
-
- private static final String SYMBOL_COMMENT = "#"; //$NON-NLS-1$
-
- private static final String PROVIDER_IN_SYSTEM_PROPERTY = "java.nio.channels.spi.SelectorProvider"; //$NON-NLS-1$
-
- private static final String PROVIDER_IN_JAR_RESOURCE = "META-INF/services/java.nio.channels.spi.SelectorProvider"; //$NON-NLS-1$
-
+public abstract class SelectorProvider {
private static SelectorProvider provider = null;
private static Channel inheritedChannel;
@@ -87,103 +76,29 @@
* @return the provider.
*/
synchronized public static SelectorProvider provider() {
- if (null == provider) {
- provider = loadProviderByProperty();
- if (null == provider) {
+ if (provider == null) {
+ provider = ServiceLoader.loadFromSystemProperty(SelectorProvider.class);
+ if (provider == null) {
provider = loadProviderByJar();
}
- if (null == provider) {
- provider = AccessController
- .doPrivileged(new PrivilegedAction<SelectorProvider>() {
- public SelectorProvider run() {
- return new SelectorProviderImpl();
- }
- });
+ if (provider == null) {
+ provider = AccessController.doPrivileged(new PrivilegedAction<SelectorProvider>() {
+ public SelectorProvider run() {
+ return new SelectorProviderImpl();
+ }
+ });
}
}
return provider;
}
- /*
- * load the provider in the jar file of class path.
- */
- static SelectorProvider loadProviderByJar() {
- Enumeration<URL> enumeration = null;
-
- ClassLoader classLoader = AccessController
- .doPrivileged(new PrivilegedAction<ClassLoader>() {
- public ClassLoader run() {
- return ClassLoader.getSystemClassLoader();
- }
- });
- try {
- enumeration = classLoader.getResources(PROVIDER_IN_JAR_RESOURCE);
- } catch (IOException e) {
- throw new Error(e);
- }
- if (null == enumeration) {
- return null;
- }
- // for every jar, read until we find the provider name.
- while (enumeration.hasMoreElements()) {
- BufferedReader br = null;
- String className = null;
- try {
- br = new BufferedReader(
- new InputStreamReader(enumeration.nextElement().openStream()));
- } catch (Exception e) {
- continue;
- }
- try {
- // only the first class is loaded ,as spec says, not the same as
- // we do before.
- while ((className = br.readLine()) != null) {
- className = className.trim();
- int siteComment = className.indexOf(SYMBOL_COMMENT);
- className = (-1 == siteComment) ? className : className
- .substring(0, siteComment);
- if (0 < className.length()) {
- return (SelectorProvider) classLoader.loadClass(
- className).newInstance();
- }
- }
- } catch (Exception e) {
- throw new Error(e);
- } finally {
- try {
- br.close();
- } catch (IOException ioe) {
- // Ignore
- }
- }
+ private static SelectorProvider loadProviderByJar() {
+ for (SelectorProvider provider : ServiceLoader.load(SelectorProvider.class, null)) {
+ return provider;
}
return null;
}
- /*
- * Load by system property.
- */
- static SelectorProvider loadProviderByProperty() {
- return AccessController
- .doPrivileged(new PrivilegedAction<SelectorProvider>() {
- public SelectorProvider run() {
- try {
- final String className = System
- .getProperty(PROVIDER_IN_SYSTEM_PROPERTY);
- if (null != className) {
- Class<?> spClass = ClassLoader
- .getSystemClassLoader().loadClass(
- className);
- return (SelectorProvider) spClass.newInstance();
- }
- return null;
- } catch (Exception e) {
- throw new Error(e);
- }
- }
- });
- }
-
/**
* Creates a new open {@code DatagramChannel}.
*
diff --git a/libcore/prefs/src/main/java/java/util/prefs/Preferences.java b/libcore/prefs/src/main/java/java/util/prefs/Preferences.java
index b128858..e79d9eb 100644
--- a/libcore/prefs/src/main/java/java/util/prefs/Preferences.java
+++ b/libcore/prefs/src/main/java/java/util/prefs/Preferences.java
@@ -16,14 +16,6 @@
package java.util.prefs;
-// BEGIN android-added
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.InputStreamReader;
-import java.net.URL;
-import java.util.Enumeration;
-// END android-added
-
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -31,7 +23,7 @@
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Locale;
-
+import java.util.ServiceLoader;
import org.apache.harmony.prefs.internal.nls.Messages;
/**
@@ -110,123 +102,24 @@
*/
public static final int MAX_VALUE_LENGTH = 8192;
- // BEGIN android-added
- /**
- * The name of the configuration file where preferences factory class names
- * can be specified.
- */
- private static final String FACTORY_CONFIGURATION_FILE_NAME = "META-INF/services/java.util.prefs.PreferencesFactory"; //$NON-NLS-1$
-
- /**
- * The encoding of configuration files
- */
- private static final String CONFIGURATION_FILE_ENCODING = "UTF-8"; //$NON-NLS-1$
-
- /**
- * The comment string used in configuration files
- */
- private static final String CONFIGURATION_FILE_COMMENT = "#"; //$NON-NLS-1$
-
- // END android-added
-
//permission
- private static final RuntimePermission PREFS_PERM = new RuntimePermission("preferences"); //$NON-NLS-1$
+ private static final RuntimePermission PREFS_PERM = new RuntimePermission("preferences");
//factory used to get user/system prefs root
- private static final PreferencesFactory factory;
+ private static final PreferencesFactory factory = findPreferencesFactory();
- // BEGIN android-removed
- // // default provider factory name for Windows
- // private static final String DEFAULT_FACTORY_NAME_WIN = "java.util.prefs.RegistryPreferencesFactoryImpl"; //$NON-NLS-1$
- //
- // // default provider factory name for Unix
- // private static final String DEFAULT_FACTORY_NAME_UNIX = "java.util.prefs.FilePreferencesFactoryImpl"; //$NON-NLS-1$
- // END android-removed
-
- static {
- String factoryClassName = AccessController.doPrivileged(new PrivilegedAction<String>() {
- public String run() {
- return System.getProperty("java.util.prefs.PreferencesFactory"); //$NON-NLS-1$
- }
- });
- // BEGIN android-removed
- // // set default provider
- // if (factoryClassName == null) {
- // String osName = AccessController.doPrivileged(new PrivilegedAction<String>() {
- // public String run() {
- // return System.getProperty("os.name"); //$NON-NLS-1$
- // }
- // });
- //
- // // only comparing ASCII, so assume english locale
- // osName = (osName == null ? null : osName.toLowerCase(Locale.ENGLISH));
- //
- // if (osName != null && osName.startsWith("windows")) {
- // factoryClassName = DEFAULT_FACTORY_NAME_WIN;
- // } else {
- // factoryClassName = DEFAULT_FACTORY_NAME_UNIX;
- // }
- // }
- // try {
- // ClassLoader loader = Thread.currentThread().getContextClassLoader();
- // if(loader == null){
- // loader = ClassLoader.getSystemClassLoader();
- // }
- // Class<?> factoryClass = loader.loadClass(factoryClassName);
- // factory = (PreferencesFactory) factoryClass.newInstance();
- // } catch (Exception e) {
- // // prefs.10=Cannot initiate PreferencesFactory: {0}. Caused by {1}
- // throw new InternalError(Messages.getString("prefs.10", factoryClassName, e)); //$NON-NLS-1$
- // }
- // BEGIN android-added
- ClassLoader loader = Thread.currentThread().getContextClassLoader();
- if (loader == null) {
- loader = ClassLoader.getSystemClassLoader();
+ private static PreferencesFactory findPreferencesFactory() {
+ // Try the system property first...
+ PreferencesFactory result = ServiceLoader.loadFromSystemProperty(PreferencesFactory.class);
+ if (result != null) {
+ return result;
}
- if (factoryClassName == null) {
- Enumeration<URL> en = null;
- try {
- en = loader.getResources(FACTORY_CONFIGURATION_FILE_NAME);
- BufferedReader reader = null;
- int commentIndex = 0;
- while (en.hasMoreElements()) {
- try {
- InputStream is = en.nextElement().openStream();
- // Read each line for charset provider class names
- reader = new BufferedReader(new InputStreamReader(is,
- CONFIGURATION_FILE_ENCODING));
- factoryClassName = reader.readLine();
- commentIndex = factoryClassName.indexOf(CONFIGURATION_FILE_COMMENT);
- if (commentIndex > 0) {
- factoryClassName = factoryClassName.substring(0, commentIndex).trim();
- }
- if (factoryClassName.length() > 0) {
- break;
- }
- } catch (IOException ex) {
- // ignore if a resource couldn't be read
- }
- }
- } catch (Exception e) {
- // prefs.10=Cannot initiate PreferencesFactory: {0}. Caused by
- // {1}
- throw new InternalError(Messages.getString("prefs.10",
- FACTORY_CONFIGURATION_FILE_NAME, e)); //$NON-NLS-1$
- }
+ // Then use ServiceLoader for META-INF/services/...
+ for (PreferencesFactory impl : ServiceLoader.load(PreferencesFactory.class, null)) {
+ return impl;
}
-
- if (factoryClassName == null) {
- factoryClassName = "java.util.prefs.FilePreferencesFactoryImpl";
- }
-
- try {
- Class<?> c = loader.loadClass(factoryClassName);
- factory = (PreferencesFactory)c.newInstance();
- } catch (Exception e) {
- // prefs.10=Cannot initiate PreferencesFactory: {0}. Caused by {1}
- throw new InternalError(Messages.getString("prefs.10", factoryClassName, e)); //$NON-NLS-1$
- }
- // END android-added
+ // Finally return a default...
+ return new FilePreferencesFactoryImpl();
}
/**
diff --git a/libcore/prefs/src/main/resources/META-INF/services/java.util.prefs.PreferencesFactory b/libcore/prefs/src/main/resources/META-INF/services/java.util.prefs.PreferencesFactory
deleted file mode 100644
index ebb514c..0000000
--- a/libcore/prefs/src/main/resources/META-INF/services/java.util.prefs.PreferencesFactory
+++ /dev/null
@@ -1 +0,0 @@
-java.util.prefs.FilePreferencesFactoryImpl
diff --git a/libcore/security/src/main/java/org/apache/harmony/security/provider/cert/X509CertFactoryImpl.java b/libcore/security/src/main/java/org/apache/harmony/security/provider/cert/X509CertFactoryImpl.java
index f0e47d9..cc175df 100644
--- a/libcore/security/src/main/java/org/apache/harmony/security/provider/cert/X509CertFactoryImpl.java
+++ b/libcore/security/src/main/java/org/apache/harmony/security/provider/cert/X509CertFactoryImpl.java
@@ -800,26 +800,17 @@
this.inStream = inStream;
}
- /**
- * @see java.io.InputStream#available()
- * method documentation for more info
- */
+ @Override
public int available() throws IOException {
return (bar - pos) + inStream.available();
}
- /**
- * @see java.io.InputStream#close()
- * method documentation for more info
- */
+ @Override
public void close() throws IOException {
inStream.close();
}
- /**
- * @see java.io.InputStream#mark(int readlimit)
- * method documentation for more info
- */
+ @Override
public void mark(int readlimit) {
if (pos < 0) {
pos = 0;
@@ -830,10 +821,7 @@
}
}
- /**
- * @see java.io.InputStream#markSupported()
- * method documentation for more info
- */
+ @Override
public boolean markSupported() {
return true;
}
@@ -881,18 +869,12 @@
return inStream.read();
}
- /**
- * @see java.io.InputStream#read(byte[] b)
- * method documentation for more info
- */
+ @Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
- /**
- * @see java.io.InputStream#read(byte[] b, int off, int len)
- * method documentation for more info
- */
+ @Override
public int read(byte[] b, int off, int len) throws IOException {
int read_b;
int i;
@@ -905,10 +887,7 @@
return i;
}
- /**
- * @see java.io.InputStream#reset()
- * method documentation for more info
- */
+ @Override
public void reset() throws IOException {
if (pos >= 0) {
pos = (end + 1) % BUFF_SIZE;
@@ -918,10 +897,7 @@
}
}
- /**
- * @see java.io.InputStream#skip(long n)
- * method documentation for more info
- */
+ @Override
public long skip(long n) throws IOException {
if (pos >= 0) {
long i = 0;
@@ -939,6 +915,3 @@
}
}
}
-
-
-
diff --git a/libcore/sqlite-jdbc/src/main/java/SQLite/Blob.java b/libcore/sqlite-jdbc/src/main/java/SQLite/Blob.java
index 3e28225..2724670 100644
--- a/libcore/sqlite-jdbc/src/main/java/SQLite/Blob.java
+++ b/libcore/sqlite-jdbc/src/main/java/SQLite/Blob.java
@@ -30,11 +30,7 @@
this.pos = 0;
}
- /**
- * Return number of available bytes for reading.
- * @return available input bytes
- */
-
+ @Override
public int available() throws IOException {
int ret = blob.size - pos;
return (ret < 0) ? 0 : ret;
diff --git a/libcore/tools/runner/java/vogar/DeviceDalvikVm.java b/libcore/tools/runner/java/vogar/DeviceDalvikVm.java
index fa96e82..4b44f02 100644
--- a/libcore/tools/runner/java/vogar/DeviceDalvikVm.java
+++ b/libcore/tools/runner/java/vogar/DeviceDalvikVm.java
@@ -29,9 +29,10 @@
DeviceDalvikVm(Integer debugPort, File sdkJar, List<String> javacArgs,
int monitorPort, File localTemp, List<String> additionalVmArgs,
- boolean cleanBefore, boolean cleanAfter, File runnerDir) {
+ List<String> targetArgs, boolean cleanBefore, boolean cleanAfter,
+ File runnerDir) {
super(new EnvironmentDevice(cleanBefore, cleanAfter, debugPort, monitorPort, localTemp,
- runnerDir), sdkJar, javacArgs, additionalVmArgs, monitorPort);
+ runnerDir), sdkJar, javacArgs, additionalVmArgs, targetArgs, monitorPort);
}
private EnvironmentDevice getEnvironmentDevice() {
diff --git a/libcore/tools/runner/java/vogar/HostMonitor.java b/libcore/tools/runner/java/vogar/HostMonitor.java
index 9732b66..c5e84bf 100644
--- a/libcore/tools/runner/java/vogar/HostMonitor.java
+++ b/libcore/tools/runner/java/vogar/HostMonitor.java
@@ -39,8 +39,11 @@
private static final Logger logger = Logger.getLogger(HostMonitor.class.getName());
- private final int MAX_CONNECT_ATTEMPTS = 10;
- private final int CONNECTION_ATTEMPT_DELAY_MILLIS = 1000;
+ private final long monitorTimeoutSeconds;
+
+ HostMonitor(long monitorTimeoutSeconds) {
+ this.monitorTimeoutSeconds = monitorTimeoutSeconds;
+ }
/**
* Connect to the target process on the given port, read all of its
@@ -65,16 +68,16 @@
return false;
}
- if (attempt++ == MAX_CONNECT_ATTEMPTS) {
- logger.warning("Exceeded " + MAX_CONNECT_ATTEMPTS
+ if (attempt++ == monitorTimeoutSeconds) {
+ logger.warning("Exceeded " + monitorTimeoutSeconds
+ " attempts to connect to localhost:" + port);
return false;
}
logger.fine("connection " + attempt + " to localhost:" + port
- + " failed; retrying in " + CONNECTION_ATTEMPT_DELAY_MILLIS + "ms");
+ + " failed; retrying in 1s");
try {
- Thread.sleep(CONNECTION_ATTEMPT_DELAY_MILLIS);
+ Thread.sleep(1000);
} catch (InterruptedException e) {
}
} while (true);
diff --git a/libcore/tools/runner/java/vogar/JavaVm.java b/libcore/tools/runner/java/vogar/JavaVm.java
index dcb0969..f728744 100644
--- a/libcore/tools/runner/java/vogar/JavaVm.java
+++ b/libcore/tools/runner/java/vogar/JavaVm.java
@@ -28,9 +28,9 @@
JavaVm(Integer debugPort, File sdkJar, List<String> javacArgs, int monitorPort,
File localTemp, File javaHome, List<String> additionalVmArgs,
- boolean cleanBefore, boolean cleanAfter) {
+ List<String> targetArgs, boolean cleanBefore, boolean cleanAfter) {
super(new EnvironmentHost(cleanBefore, cleanAfter, debugPort, localTemp),
- sdkJar, javacArgs, additionalVmArgs, monitorPort);
+ sdkJar, javacArgs, additionalVmArgs, targetArgs, monitorPort);
this.javaHome = javaHome;
}
diff --git a/libcore/tools/runner/java/vogar/Vm.java b/libcore/tools/runner/java/vogar/Vm.java
index c69d7a2..4edb0bc 100644
--- a/libcore/tools/runner/java/vogar/Vm.java
+++ b/libcore/tools/runner/java/vogar/Vm.java
@@ -32,11 +32,13 @@
public abstract class Vm extends Mode {
protected final List<String> additionalVmArgs;
+ protected final List<String> targetArgs;
Vm(Environment environment, File sdkJar, List<String> javacArgs,
- List<String> additionalVmArgs, int monitorPort) {
+ List<String> additionalVmArgs, List<String> targetArgs, int monitorPort) {
super(environment, sdkJar, javacArgs, monitorPort);
this.additionalVmArgs = additionalVmArgs;
+ this.targetArgs = targetArgs;
}
/**
@@ -49,6 +51,7 @@
.debugPort(environment.debugPort)
.vmArgs(additionalVmArgs)
.mainClass(TestRunner.class.getName())
+ .args(targetArgs)
.build();
}
@@ -76,6 +79,7 @@
private PrintStream output;
private List<String> vmCommand = Collections.singletonList("java");
private List<String> vmArgs = new ArrayList<String>();
+ private List<String> args = new ArrayList<String>();
public VmCommandBuilder vmCommand(String... vmCommand) {
this.vmCommand = Arrays.asList(vmCommand.clone());
@@ -126,6 +130,15 @@
return this;
}
+ public VmCommandBuilder args(String... args) {
+ return args(Arrays.asList(args));
+ }
+
+ public VmCommandBuilder args(Collection<String> args) {
+ this.args.addAll(args);
+ return this;
+ }
+
public Command build() {
Command.Builder builder = new Command.Builder();
builder.args(vmCommand);
@@ -146,6 +159,7 @@
builder.args(vmArgs);
builder.args(mainClass);
+ builder.args(args);
builder.tee(output);
diff --git a/libcore/tools/runner/java/vogar/Vogar.java b/libcore/tools/runner/java/vogar/Vogar.java
index bca0a3d..2e9e3a9 100644
--- a/libcore/tools/runner/java/vogar/Vogar.java
+++ b/libcore/tools/runner/java/vogar/Vogar.java
@@ -36,6 +36,7 @@
private static class Options {
private final List<File> actionFiles = new ArrayList<File>();
+ private final List<String> targetArgs = new ArrayList<String>();
@Option(names = { "--expectations" })
private Set<File> expectationFiles = new LinkedHashSet<File>();
@@ -52,6 +53,9 @@
@Option(names = { "--timeout" })
private long timeoutSeconds = 10 * 60; // default is ten minutes;
+ @Option(names = { "--monitor-timeout" })
+ private long monitorTimeout = 10;
+
@Option(names = { "--clean-before" })
private boolean cleanBefore = true;
@@ -95,53 +99,43 @@
private File sdkJar = new File("/home/dalvik-prebuild/android-sdk-linux/platforms/android-2.0/android.jar");
private void printUsage() {
- System.out.println("Usage: Vogar [options]... <actions>...");
+ System.out.println("Usage: Vogar [options]... <actions>... [target args]...");
System.out.println();
- System.out.println(" <actions>: .java files containing a jtreg tests, JUnit tests,");
- System.out.println(" Caliper benchmarks, or a directory of such tests.");
+ System.out.println(" <actions>: .java files or directories containing jtreg tests, JUnit");
+ System.out.println(" tests, Caliper benchmarks, or executable Java classes.");
+ System.out.println();
+ System.out.println(" [args]: arguments passed to the target process. This is only useful when");
+ System.out.println(" the target process is a Caliper benchmark or main method.");
System.out.println();
System.out.println("GENERAL OPTIONS");
System.out.println();
- System.out.println(" --expectations <file>: include the specified file when looking for");
- System.out.println(" action expectations. The file should include qualified action names");
- System.out.println(" and the corresponding expected output.");
- System.out.println(" Default is: " + expectationFiles);
- System.out.println();
System.out.println(" --mode <device|host|activity>: specify which environment to run the");
System.out.println(" actions in. Options are on the device VM, on the host VM, and on");
System.out.println(" device within an android.app.Activity.");
System.out.println(" Default is: " + mode);
System.out.println();
- System.out.println(" --clean-before: remove working directories before building and");
- System.out.println(" running (default). Disable with --no-clean-before if you are");
- System.out.println(" using interactively with your own temporary input files.");
- System.out.println();
- System.out.println(" --clean-after: remove temporary files after running (default).");
- System.out.println(" Disable with --no-clean-after and use with --verbose if");
- System.out.println(" you'd like to manually re-run commands afterwards.");
- System.out.println();
System.out.println(" --clean: synonym for --clean-before and --clean-after (default).");
System.out.println(" Disable with --no-clean if you want no files removed.");
System.out.println();
- System.out.println(" --color: format output in technicolor.");
- System.out.println();
System.out.println(" --stream: stream output as it is emitted.");
System.out.println();
- System.out.println(" --timeout-seconds <seconds>: maximum execution time of each");
- System.out.println(" action before the runner aborts it. Specifying zero seconds");
- System.out.println(" or using --debug will disable the execution timeout");
+ System.out.println(" --timeout <seconds>: maximum execution time of each action before the");
+ System.out.println(" runner aborts it. Specifying zero seconds or using --debug will");
+ System.out.println(" disable the execution timeout.");
System.out.println(" Default is: " + timeoutSeconds);
System.out.println();
System.out.println(" --xml-reports-directory <path>: directory to emit JUnit-style");
System.out.println(" XML test results.");
System.out.println();
- System.out.println(" --ident: amount to indent action result output. Can be set to ''");
- System.out.println(" (aka empty string) to simplify output parsing.");
- System.out.println(" Default is: '" + indent + "'");
+ System.out.println(" --sdk <android jar>: the API jar file to compile against.");
+ System.out.println(" Usually this is <SDK>/platforms/android-<X.X>/android.jar");
+ System.out.println(" where <SDK> is the path to an Android SDK path and <X.X> is");
+ System.out.println(" a release version like 1.5.");
+ System.out.println(" Default is: " + sdkJar);
System.out.println();
System.out.println(" --verbose: turn on verbose output");
System.out.println();
- System.out.println("DEVICE OPTIONS");
+ System.out.println("TARGET OPTIONS");
System.out.println();
System.out.println(" --debug <port>: enable Java debugging on the specified port.");
System.out.println(" This port must be free both on the device and on the local");
@@ -151,34 +145,47 @@
System.out.println(" on-device temporary files and code.");
System.out.println(" Default is: " + deviceRunnerDir);
System.out.println();
- System.out.println("GENERAL VM OPTIONS");
- System.out.println();
System.out.println(" --vm-arg <argument>: include the specified argument when spawning a");
System.out.println(" virtual machine. Examples: -Xint:fast, -ea, -Xmx16M");
System.out.println();
- System.out.println("HOST VM OPTIONS");
- System.out.println();
System.out.println(" --java-home <java_home>: execute the actions on the local workstation");
System.out.println(" using the specified java home directory. This does not impact");
System.out.println(" which javac gets used. When unset, java is used from the PATH.");
System.out.println();
- System.out.println("COMPILE OPTIONS");
+ System.out.println("EXOTIC OPTIONS");
System.out.println();
- System.out.println(" --sdk <android jar>: the API jar file to compile against.");
- System.out.println(" Usually this is <SDK>/platforms/android-<X.X>/android.jar");
- System.out.println(" where <SDK> is the path to an Android SDK path and <X.X> is");
- System.out.println(" a release version like 1.5.");
- System.out.println(" Default is: " + sdkJar);
+ System.out.println(" --clean-before: remove working directories before building and");
+ System.out.println(" running (default). Disable with --no-clean-before if you are");
+ System.out.println(" using interactively with your own temporary input files.");
+ System.out.println();
+ System.out.println(" --clean-after: remove temporary files after running (default).");
+ System.out.println(" Disable with --no-clean-after and use with --verbose if");
+ System.out.println(" you'd like to manually re-run commands afterwards.");
+ System.out.println();
+ System.out.println(" --color: format output in technicolor.");
+ System.out.println();
+ System.out.println(" --expectations <file>: include the specified file when looking for");
+ System.out.println(" action expectations. The file should include qualified action names");
+ System.out.println(" and the corresponding expected output.");
+ System.out.println(" Default is: " + expectationFiles);
+ System.out.println();
+ System.out.println(" --ident: amount to indent action result output. Can be set to ''");
+ System.out.println(" (aka empty string) to simplify output parsing.");
+ System.out.println(" Default is: '" + indent + "'");
System.out.println();
System.out.println(" --javac-arg <argument>: include the specified argument when invoking");
System.out.println(" javac. Examples: --javac-arg -Xmaxerrs --javac-arg 1");
System.out.println();
+ System.out.println(" --monitor-timeout <seconds>: number of seconds to wait for the target");
+ System.out.println(" process to launch. This can be used to prevent connection failures");
+ System.out.println(" when dexopt is slow.");
+ System.out.println();
}
private boolean parseArgs(String[] args) {
- final List<String> actionFilenames;
+ List<String> actionsAndTargetArgs;
try {
- actionFilenames = new OptionParser(this).parse(args);
+ actionsAndTargetArgs = new OptionParser(this).parse(args);
} catch (RuntimeException e) {
System.out.println(e.getMessage());
return false;
@@ -236,11 +243,6 @@
return false;
}
- if (actionFilenames.isEmpty()) {
- System.out.println("No actions provided.");
- return false;
- }
-
if (!clean) {
cleanBefore = false;
cleanAfter = false;
@@ -255,13 +257,35 @@
timeoutSeconds = 0;
}
- for (String actionFilename : actionFilenames) {
- actionFiles.add(new File(actionFilename));
+ // separate the actions and the target args
+ boolean action = true;
+ for (String arg : actionsAndTargetArgs) {
+ if (arg.equals("--")) {
+ action = false;
+ continue;
+ }
+
+ File actionFile = new File(arg);
+ if (action && actionFile.exists()) {
+ actionFiles.add(actionFile);
+ } else {
+ targetArgs.add(arg);
+ action = false;
+ }
+ }
+
+ if (actionFiles.isEmpty()) {
+ System.out.println("No actions provided.");
+ return false;
+ }
+
+ if (!targetArgs.isEmpty() && mode.equals(Options.MODE_ACTIVITY)) {
+ System.out.println("Target args not supported with --mode activity");
+ return false;
}
return true;
}
-
}
private final Options options = new Options();
@@ -284,6 +308,7 @@
monitorPort,
localTemp,
options.vmArgs,
+ options.targetArgs,
options.cleanBefore,
options.cleanAfter,
options.deviceRunnerDir);
@@ -297,6 +322,7 @@
localTemp,
options.javaHome,
options.vmArgs,
+ options.targetArgs,
options.cleanBefore,
options.cleanAfter
);
@@ -316,7 +342,7 @@
return;
}
- HostMonitor monitor = new HostMonitor();
+ HostMonitor monitor = new HostMonitor(options.monitorTimeout);
List<CodeFinder> codeFinders = Arrays.asList(
new JtregFinder(localTemp),
diff --git a/libcore/tools/runner/java/vogar/target/CaliperRunner.java b/libcore/tools/runner/java/vogar/target/CaliperRunner.java
index 5b424c8..3336716 100644
--- a/libcore/tools/runner/java/vogar/target/CaliperRunner.java
+++ b/libcore/tools/runner/java/vogar/target/CaliperRunner.java
@@ -32,10 +32,10 @@
this.monitor = monitor;
}
- public void run(String actionName, Class<?> testClass) {
+ public void run(String actionName, Class<?> testClass, String[] args) {
monitor.outcomeStarted(actionName, actionName);
try {
- Runner.main(testClass.asSubclass(Benchmark.class), new String[0]);
+ Runner.main(testClass.asSubclass(Benchmark.class), args);
} catch (Exception ex) {
ex.printStackTrace();
}
diff --git a/libcore/tools/runner/java/vogar/target/JUnitRunner.java b/libcore/tools/runner/java/vogar/target/JUnitRunner.java
index 00c61f8..f15cad3 100644
--- a/libcore/tools/runner/java/vogar/target/JUnitRunner.java
+++ b/libcore/tools/runner/java/vogar/target/JUnitRunner.java
@@ -57,7 +57,7 @@
this.junitTest = testRunner.getTest(testClass.getName());
}
- public void run(String actionName, Class<?> testClass) {
+ public void run(String actionName, Class<?> testClass, String[] args) {
testRunner.doRun(junitTest);
}
diff --git a/libcore/tools/runner/java/vogar/target/JtregRunner.java b/libcore/tools/runner/java/vogar/target/JtregRunner.java
index 6deeef9..47090c5 100644
--- a/libcore/tools/runner/java/vogar/target/JtregRunner.java
+++ b/libcore/tools/runner/java/vogar/target/JtregRunner.java
@@ -37,10 +37,10 @@
}
}
- public void run(String actionName, Class<?> testClass) {
+ public void run(String actionName, Class<?> testClass, String[] args) {
monitor.outcomeStarted(actionName, actionName);
try {
- main.invoke(null, new Object[] { new String[0] });
+ main.invoke(null, new Object[] { args });
monitor.outcomeFinished(Result.SUCCESS);
} catch (Throwable failure) {
failure.printStackTrace();
diff --git a/libcore/tools/runner/java/vogar/target/MainRunner.java b/libcore/tools/runner/java/vogar/target/MainRunner.java
index cc7dccd..d4e5dec 100644
--- a/libcore/tools/runner/java/vogar/target/MainRunner.java
+++ b/libcore/tools/runner/java/vogar/target/MainRunner.java
@@ -37,10 +37,10 @@
}
}
- public void run(String actionName, Class<?> testClass) {
+ public void run(String actionName, Class<?> testClass, String[] args) {
monitor.outcomeStarted(actionName, actionName);
try {
- main.invoke(null, new Object[] { new String[0] });
+ main.invoke(null, new Object[] { args });
} catch (Throwable ex) {
ex.printStackTrace();
}
diff --git a/libcore/tools/runner/java/vogar/target/Runner.java b/libcore/tools/runner/java/vogar/target/Runner.java
index af98a00..a06ba1a 100644
--- a/libcore/tools/runner/java/vogar/target/Runner.java
+++ b/libcore/tools/runner/java/vogar/target/Runner.java
@@ -25,5 +25,5 @@
public void init(TargetMonitor monitor, String actionName,
Class<?> testClass);
- public void run(String actionName, Class<?> testClass);
+ public void run(String actionName, Class<?> testClass, String[] args);
}
diff --git a/libcore/tools/runner/java/vogar/target/TestRunner.java b/libcore/tools/runner/java/vogar/target/TestRunner.java
index 1e1f479..739cbc5 100644
--- a/libcore/tools/runner/java/vogar/target/TestRunner.java
+++ b/libcore/tools/runner/java/vogar/target/TestRunner.java
@@ -75,7 +75,7 @@
}
}
- public void run() {
+ public void run(String[] args) {
final TargetMonitor monitor = new TargetMonitor();
monitor.await(monitorPort);
@@ -97,7 +97,7 @@
throw new RuntimeException(e);
}
runner.init(monitor, qualifiedName, testClass);
- runner.run(qualifiedName, testClass);
+ runner.run(qualifiedName, testClass, args);
monitor.close();
}
@@ -105,9 +105,6 @@
public static void main(String[] args) {
- if (args.length != 0) {
- throw new RuntimeException("TestRunner doesn't take arguments");
- }
- new TestRunner().run();
+ new TestRunner().run(args);
}
}
diff --git a/libcore/tools/runner/lib/TestActivity.java b/libcore/tools/runner/lib/TestActivity.java
index 6e6d09f..39a6575 100644
--- a/libcore/tools/runner/lib/TestActivity.java
+++ b/libcore/tools/runner/lib/TestActivity.java
@@ -48,7 +48,7 @@
1, Threads.daemonThreadFactory());
executor.submit(new Runnable() {
public void run() {
- new TestRunner().run();
+ new TestRunner().run(args);
}
});
executor.shutdown();
diff --git a/libcore/x-net/src/main/java/javax/net/ssl/SSLSocketFactory.java b/libcore/x-net/src/main/java/javax/net/ssl/SSLSocketFactory.java
index d9db099..b75c218 100644
--- a/libcore/x-net/src/main/java/javax/net/ssl/SSLSocketFactory.java
+++ b/libcore/x-net/src/main/java/javax/net/ssl/SSLSocketFactory.java
@@ -23,6 +23,7 @@
import java.security.PrivilegedAction;
import java.security.Security;
// BEGIN android-added
+import java.util.logging.Level;
import java.util.logging.Logger;
// END android-added
@@ -48,7 +49,7 @@
public static synchronized SocketFactory getDefault() {
if (defaultSocketFactory != null) {
// BEGIN android-added
- log("SSLSocketFactory", "Using factory " + defaultSocketFactory);
+ // log("SSLSocketFactory", "Using factory " + defaultSocketFactory, null);
// END android-added
return defaultSocketFactory;
}
@@ -65,6 +66,9 @@
final Class<?> sfc = Class.forName(defaultName, true, cl);
defaultSocketFactory = (SocketFactory) sfc.newInstance();
} catch (Exception e) {
+ // BEGIN android-added
+ log("SSLSocketFactory", "Problem creating " + defaultName, e);
+ // END android-added
}
}
return null;
@@ -83,16 +87,16 @@
// Use internal implementation
defaultSocketFactory = new DefaultSSLSocketFactory("No SSLSocketFactory installed");
}
- // BEGIN android-added
- log("SSLSocketFactory", "Using factory " + defaultSocketFactory);
- // END android-added
+ // BEGIN android-added
+ // log("SSLSocketFactory", "Using factory " + defaultSocketFactory, null);
+ // END android-added
return defaultSocketFactory;
}
// BEGIN android-added
@SuppressWarnings("unchecked")
- private static void log(String tag, String msg) {
- Logger.getLogger(tag).info(msg);
+ private static void log(String tag, String msg, Throwable throwable) {
+ Logger.getLogger(tag).log(Level.INFO, msg, throwable);
}
// END android-added
diff --git a/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/AbstractSessionContext.java b/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/AbstractSessionContext.java
index a95d38f..b780943 100644
--- a/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/AbstractSessionContext.java
+++ b/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/AbstractSessionContext.java
@@ -35,6 +35,7 @@
volatile int timeout;
final SSLParameters parameters;
+ final int sslCtxNativePointer;
/** Identifies OpenSSL sessions. */
static final int OPEN_SSL = 1;
@@ -46,9 +47,10 @@
* @param maximumSize of cache
* @param timeout for cache entries
*/
- AbstractSessionContext(SSLParameters parameters, int maximumSize,
- int timeout) {
+ AbstractSessionContext(SSLParameters parameters, int sslCtxNativePointer,
+ int maximumSize, int timeout) {
this.parameters = parameters;
+ this.sslCtxNativePointer = sslCtxNativePointer;
this.maximumSize = maximumSize;
this.timeout = timeout;
}
@@ -181,11 +183,20 @@
}
}
+ /**
+ * Puts an SSLSession in the AbstractSessionContext cache
+ */
+ abstract void putSession(SSLSession session);
+
static void log(Throwable t) {
java.util.logging.Logger.global.log(Level.WARNING,
"Error converting session.", t);
}
+ protected void finalize() throws IOException {
+ NativeCrypto.SSL_CTX_free(sslCtxNativePointer);
+ }
+
/**
* Byte array wrapper. Implements equals() and hashCode().
*/
@@ -209,4 +220,4 @@
return Arrays.equals(bytes, other.bytes);
}
}
-}
\ No newline at end of file
+}
diff --git a/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/CertificateRequest.java b/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/CertificateRequest.java
index 7b27787..7246c4d 100644
--- a/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/CertificateRequest.java
+++ b/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/CertificateRequest.java
@@ -55,7 +55,7 @@
*/
X500Principal[] certificate_authorities;
- //Requested certificate types as Strings
+ // Requested certificate types as Strings
// ("RSA", "DSA", "DH_RSA" or "DH_DSA")
private String[] types;
diff --git a/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientSessionContext.java b/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientSessionContext.java
index 2c8738f..338fc39 100644
--- a/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientSessionContext.java
+++ b/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientSessionContext.java
@@ -64,8 +64,9 @@
final SSLClientSessionCache persistentCache;
public ClientSessionContext(SSLParameters parameters,
+ int sslCtxNativePointer,
SSLClientSessionCache persistentCache) {
- super(parameters, 10, 0);
+ super(parameters, sslCtxNativePointer, 10, 0);
this.persistentCache = persistentCache;
}
@@ -144,9 +145,9 @@
* Adds the given session to the ID-based index if the index has already
* been initialized.
*/
- private void indexById(SSLSession session) {
+ private void indexById(byte[] id, SSLSession session) {
if (sessionsById != null) {
- sessionsById.put(new ByteArray(session.getId()), session);
+ sessionsById.put(new ByteArray(id), session);
}
}
@@ -173,7 +174,7 @@
if (session != null) {
synchronized (sessions) {
sessions.put(new HostAndPort(host, port), session);
- indexById(session);
+ indexById(session.getId(), session);
}
return session;
}
@@ -183,12 +184,17 @@
return null;
}
+ @Override
void putSession(SSLSession session) {
+ byte[] id = session.getId();
+ if (id.length == 0) {
+ return;
+ }
HostAndPort key = new HostAndPort(session.getPeerHost(),
session.getPeerPort());
synchronized (sessions) {
sessions.put(key, session);
- indexById(session);
+ indexById(id, session);
}
// TODO: This in a background thread.
diff --git a/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/JSSEProvider.java b/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/JSSEProvider.java
index 33b0a45..083a342 100644
--- a/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/JSSEProvider.java
+++ b/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/JSSEProvider.java
@@ -115,6 +115,8 @@
put("KeyManagerFactory.X509", KeyManagerFactoryImpl.class.getName());
put("TrustManagerFactory.X509", TrustManagerFactoryImpl.class.getName());
// BEGIN android-added
+ put("SSLContext.SSL", SSLContextImpl.class.getName());
+ put("Alg.Alias.SSLContext.SSLv3", "SSL");
put("MessageDigest.SHA-1", "org.apache.harmony.xnet.provider.jsse.OpenSSLMessageDigestJDK$SHA1");
put("Alg.Alias.MessageDigest.SHA1", "SHA-1");
put("Alg.Alias.MessageDigest.SHA", "SHA-1");
diff --git a/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java b/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java
index 7698035..ad6ae15 100644
--- a/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java
+++ b/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java
@@ -16,6 +16,15 @@
package org.apache.harmony.xnet.provider.jsse;
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+import org.bouncycastle.openssl.PEMWriter;
+
/**
* Provides the Java side of our JNI glue for OpenSSL. Currently only hashing
* and verifying are covered. Is expected to grow over time. Also needs to move
@@ -23,31 +32,31 @@
*/
public class NativeCrypto {
+ // --- OpenSSL library initialization --------------------------------------
static {
- // Need to ensure that OpenSSL initialization is done exactly once.
- // This can be cleaned up later, when all OpenSSL glue moves into its
- // own libcore module. Make it run, make it nice.
- OpenSSLSocketImpl.class.getClass();
+ clinit();
}
+ private native static void clinit();
+
// --- DSA/RSA public/private key handling functions -----------------------
-
+
public static native int EVP_PKEY_new_DSA(byte[] p, byte[] q, byte[] g, byte[] priv_key, byte[] pub_key);
public static native int EVP_PKEY_new_RSA(byte[] n, byte[] e, byte[] d, byte[] p, byte[] q);
-
+
public static native void EVP_PKEY_free(int pkey);
-
+
// --- General context handling functions (despite the names) --------------
-
+
public static native int EVP_new();
-
+
public static native void EVP_free(int ctx);
-
+
// --- Digest handling functions -------------------------------------------
-
+
public static native void EVP_DigestInit(int ctx, String algorithm);
-
+
public static native void EVP_DigestUpdate(int ctx, byte[] buffer, int offset, int length);
public static native int EVP_DigestFinal(int ctx, byte[] hash, int offset);
@@ -55,13 +64,186 @@
public static native int EVP_DigestSize(int ctx);
public static native int EVP_DigestBlockSize(int ctx);
-
+
// --- Signature handling functions ----------------------------------------
-
+
public static native void EVP_VerifyInit(int ctx, String algorithm);
-
+
public static native void EVP_VerifyUpdate(int ctx, byte[] buffer, int offset, int length);
-
+
public static native int EVP_VerifyFinal(int ctx, byte[] signature, int offset, int length, int key);
-
+
+ // --- SSL handling --------------------------------------------------------
+
+ private static final String SUPPORTED_PROTOCOL_SSLV3 = "SSLv3";
+ private static final String SUPPORTED_PROTOCOL_TLSV1 = "TLSv1";
+
+ public static long SSL_OP_NO_SSLv3 = 0x02000000L;
+ public static long SSL_OP_NO_TLSv1 = 0x04000000L;
+
+ public static native int SSL_CTX_new();
+
+ public static native String[] SSL_CTX_get_ciphers(int ssl_ctx);
+
+ public static String[] getDefaultCipherSuites() {
+ int ssl_ctx = SSL_CTX_new();
+ String[] supportedCiphers = SSL_CTX_get_ciphers(ssl_ctx);
+ SSL_CTX_free(ssl_ctx);
+ return supportedCiphers;
+ }
+
+ public static String[] getSupportedCipherSuites() {
+ // TODO really return full cipher list
+ return getDefaultCipherSuites();
+ }
+
+ public static native void SSL_CTX_free(int ssl_ctx);
+
+ public static native int SSL_new(int ssl_ctx, String privatekey, String certificate, byte[] seed) throws IOException;
+
+ /**
+ * Initialize the SSL socket and set the certificates for the
+ * future handshaking.
+ */
+ public static int SSL_new(SSLParameters sslParameters) throws IOException {
+ boolean client = sslParameters.getUseClientMode();
+
+ final int ssl_ctx = (client) ?
+ sslParameters.getClientSessionContext().sslCtxNativePointer :
+ sslParameters.getServerSessionContext().sslCtxNativePointer;
+
+ // TODO support more than RSA certificates? non-openssl
+ // SSLEngine implementation did these callbacks during
+ // handshake after selecting cipher suite, not before
+ // handshake.
+ final String alias = (client) ?
+ sslParameters.getKeyManager().chooseClientAlias(new String[] { "RSA" }, null, null) :
+ sslParameters.getKeyManager().chooseServerAlias("RSA", null, null);
+
+ final String privateKeyString;
+ final String certificateString;
+ if (alias == null) {
+ privateKeyString = null;
+ certificateString = null;
+ } else {
+ PrivateKey privateKey = sslParameters.getKeyManager().getPrivateKey(alias);
+ X509Certificate[] certificates = sslParameters.getKeyManager().getCertificateChain(alias);
+
+ ByteArrayOutputStream privateKeyOS = new ByteArrayOutputStream();
+ PEMWriter privateKeyPEMWriter = new PEMWriter(new OutputStreamWriter(privateKeyOS));
+ privateKeyPEMWriter.writeObject(privateKey);
+ privateKeyPEMWriter.close();
+ privateKeyString = privateKeyOS.toString();
+
+ ByteArrayOutputStream certificateOS = new ByteArrayOutputStream();
+ PEMWriter certificateWriter = new PEMWriter(new OutputStreamWriter(certificateOS));
+
+ for (X509Certificate certificate : certificates) {
+ certificateWriter.writeObject(certificate);
+ }
+ certificateWriter.close();
+ certificateString = certificateOS.toString();
+ }
+
+ final byte[] seed = (sslParameters.getSecureRandomMember() != null) ?
+ sslParameters.getSecureRandomMember().generateSeed(1024) :
+ null;
+
+ return SSL_new(ssl_ctx,
+ privateKeyString,
+ certificateString,
+ seed);
+ }
+
+
+ public static native long SSL_get_options(int ssl);
+
+ public static native long SSL_set_options(int ssl, long options);
+
+ public static String[] getSupportedProtocols() {
+ return new String[] { SUPPORTED_PROTOCOL_SSLV3, SUPPORTED_PROTOCOL_TLSV1 };
+ }
+
+ public static String[] getEnabledProtocols(int ssl) {
+ long options = SSL_get_options(ssl);
+ ArrayList<String> array = new ArrayList<String>();
+ if ((options & NativeCrypto.SSL_OP_NO_SSLv3) == 0) {
+ array.add(SUPPORTED_PROTOCOL_SSLV3);
+ }
+ if ((options & NativeCrypto.SSL_OP_NO_TLSv1) == 0) {
+ array.add(SUPPORTED_PROTOCOL_TLSV1);
+ }
+ return array.toArray(new String[array.size()]);
+ }
+
+ public static void setEnabledProtocols(int ssl, String[] protocols) {
+ if (protocols == null) {
+ throw new IllegalArgumentException("Provided parameter is null");
+ }
+ // openssl uses negative logic letting you disable protocols.
+ // so first, lets turn them all off, and in the loop selectively enable
+ long options = SSL_get_options(ssl);
+ options |= (SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1);
+ for (int i = 0; i < protocols.length; i++) {
+ if (protocols[i].equals(SUPPORTED_PROTOCOL_SSLV3)) {
+ options ^= SSL_OP_NO_SSLv3;
+ } else if (protocols[i].equals(SUPPORTED_PROTOCOL_TLSV1)) {
+ options ^= SSL_OP_NO_TLSv1;
+ } else {
+ throw new IllegalArgumentException("Protocol " + protocols[i] +
+ " is not supported");
+ }
+ }
+ SSL_set_options(ssl, options);
+ }
+
+ public static native String[] SSL_get_ciphers(int ssl);
+
+ public static native void SSL_set_cipher_list(int ssl, String ciphers);
+
+ public static void setEnabledCipherSuites(int ssl, String[] suites) {
+ if (suites == null) {
+ throw new IllegalArgumentException("Provided parameter is null");
+ }
+
+ // makes sure all suites are valid, throwing on error
+ String[] supportedCipherSuites = getSupportedCipherSuites();
+ for (String suite : suites) {
+ findSuite(supportedCipherSuites, suite);
+ }
+
+ String controlString = "";
+ for (int i = 0; i < suites.length; i++) {
+ if (i == 0) {
+ controlString = suites[i];
+ } else {
+ controlString += ":" + suites[i];
+ }
+ }
+ SSL_set_cipher_list(ssl, controlString);
+ }
+
+ private static void findSuite(String[] supportedCipherSuites, String suite) {
+ for(int i = 0; i < supportedCipherSuites.length; i++) {
+ if (supportedCipherSuites[i].equals(suite)) {
+ return;
+ }
+ }
+ throw new IllegalArgumentException("Protocol " + suite + " is not supported.");
+ }
+
+ public static native void SSL_free(int ssl);
+
+ public interface CertificateChainVerifier {
+ /**
+ * Verify that we trust the certificate chain is trusted.
+ *
+ * @param bytes An array of certficates in byte form
+ *
+ * @throws AlertException if the certificate is untrusted
+ * @return false if there are other problems verifying the certificate chain
+ */
+ // TODO throw on error in all cases instead of returning false
+ public boolean verifyCertificateChain(byte[][] bytes);
+ }
}
diff --git a/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketFactoryImpl.java b/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketFactoryImpl.java
index 970f5dc..f342457 100644
--- a/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketFactoryImpl.java
+++ b/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketFactoryImpl.java
@@ -39,16 +39,16 @@
}
public OpenSSLServerSocketFactoryImpl(SSLParameters sslParameters) {
- this.sslParameters = sslParameters;
+ this.sslParameters = (SSLParameters) sslParameters.clone();
+ this.sslParameters.setUseClientMode(false);
}
public String[] getDefaultCipherSuites() {
- // TODO There might be a better way to implement this...
- return OpenSSLServerSocketImpl.nativegetsupportedciphersuites();
+ return NativeCrypto.getDefaultCipherSuites();
}
public String[] getSupportedCipherSuites() {
- return OpenSSLServerSocketImpl.nativegetsupportedciphersuites();
+ return NativeCrypto.getSupportedCipherSuites();
}
public ServerSocket createServerSocket() throws IOException {
diff --git a/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl.java b/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl.java
index d52f08f..c79dccf 100644
--- a/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl.java
+++ b/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl.java
@@ -16,98 +16,46 @@
package org.apache.harmony.xnet.provider.jsse;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
-import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.Socket;
-import java.security.PrivateKey;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-
-import org.bouncycastle.openssl.PEMWriter;
/**
* OpenSSL-based implementation of server sockets.
- *
+ *
* This class only supports SSLv3 and TLSv1. This should be documented elsewhere
* later, for example in the package.html or a separate reference document.
- */
+ */
public class OpenSSLServerSocketImpl extends javax.net.ssl.SSLServerSocket {
- private int ssl_ctx;
- private boolean client_mode = true;
- private long ssl_op_no = 0x00000000L;
- private SSLParameters sslParameters;
- private static final String[] supportedProtocols = new String[] {
- "SSLv3",
- "TLSv1"
- };
-
- private native static void nativeinitstatic();
-
- static {
- nativeinitstatic();
- }
-
- private native void nativeinit(String privatekey, String certificate, byte[] seed);
-
- /**
- * Initialize the SSL server socket and set the certificates for the
- * future handshaking.
- */
- private void init() throws IOException {
- String alias = sslParameters.getKeyManager().chooseServerAlias("RSA", null, null);
- if (alias == null) {
- throw new IOException("No suitable certificates found");
- }
-
- PrivateKey privateKey = sslParameters.getKeyManager().getPrivateKey(alias);
- X509Certificate[] certificates = sslParameters.getKeyManager().getCertificateChain(alias);
-
- ByteArrayOutputStream privateKeyOS = new ByteArrayOutputStream();
- PEMWriter privateKeyPEMWriter = new PEMWriter(new OutputStreamWriter(privateKeyOS));
- privateKeyPEMWriter.writeObject(privateKey);
- privateKeyPEMWriter.close();
-
- ByteArrayOutputStream certificateOS = new ByteArrayOutputStream();
- PEMWriter certificateWriter = new PEMWriter(new OutputStreamWriter(certificateOS));
-
- for (int i = 0; i < certificates.length; i++) {
- certificateWriter.writeObject(certificates[i]);
- }
- certificateWriter.close();
-
- nativeinit(privateKeyOS.toString(), certificateOS.toString(),
- sslParameters.getSecureRandomMember() != null ?
- sslParameters.getSecureRandomMember().generateSeed(1024) : null);
- }
+ private final SSLParameters sslParameters;
+ private int sslNativePointer;
protected OpenSSLServerSocketImpl(SSLParameters sslParameters)
throws IOException {
super();
this.sslParameters = sslParameters;
- init();
+ this.sslNativePointer = NativeCrypto.SSL_new(sslParameters);
}
protected OpenSSLServerSocketImpl(int port, SSLParameters sslParameters)
throws IOException {
super(port);
this.sslParameters = sslParameters;
- init();
+ this.sslNativePointer = NativeCrypto.SSL_new(sslParameters);
}
protected OpenSSLServerSocketImpl(int port, int backlog, SSLParameters sslParameters)
throws IOException {
super(port, backlog);
this.sslParameters = sslParameters;
- init();
+ this.sslNativePointer = NativeCrypto.SSL_new(sslParameters);
}
protected OpenSSLServerSocketImpl(int port, int backlog, InetAddress iAddress, SSLParameters sslParameters)
throws IOException {
super(port, backlog, iAddress);
this.sslParameters = sslParameters;
- init();
+ this.sslNativePointer = NativeCrypto.SSL_new(sslParameters);
}
@Override
@@ -127,78 +75,41 @@
*/
@Override
public String[] getSupportedProtocols() {
- return supportedProtocols.clone();
+ return NativeCrypto.getSupportedProtocols();
}
/**
- * See the OpenSSL ssl.h header file for more information.
- */
- static private long SSL_OP_NO_SSLv3 = 0x02000000L;
- static private long SSL_OP_NO_TLSv1 = 0x04000000L;
-
- /**
* The names of the protocols' versions that in use on this SSL connection.
- *
+ *
* @return an array of protocols names
*/
@Override
public String[] getEnabledProtocols() {
- ArrayList<String> array = new ArrayList<String>();
-
- if ((ssl_op_no & SSL_OP_NO_SSLv3) == 0x00000000L) {
- array.add(supportedProtocols[0]);
- }
- if ((ssl_op_no & SSL_OP_NO_TLSv1) == 0x00000000L) {
- array.add(supportedProtocols[1]);
- }
- return array.toArray(new String[array.size()]);
+ return NativeCrypto.getEnabledProtocols(sslNativePointer);
}
- private native void nativesetenabledprotocols(long l);
-
/**
* This method enables the protocols' versions listed by
* getSupportedProtocols().
- *
+ *
* @param protocols names of all the protocols to enable.
- *
+ *
* @throws IllegalArgumentException when one or more of the names in the
* array are not supported, or when the array is null.
*/
@Override
public void setEnabledProtocols(String[] protocols) {
- if (protocols == null) {
- throw new IllegalArgumentException("Provided parameter is null");
- }
-
- ssl_op_no = SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1;
-
- for (int i = 0; i < protocols.length; i++) {
- if (protocols[i].equals("SSLv3"))
- ssl_op_no ^= SSL_OP_NO_SSLv3;
- else if (protocols[i].equals("TLSv1"))
- ssl_op_no ^= SSL_OP_NO_TLSv1;
- else throw new IllegalArgumentException("Protocol " + protocols[i] +
- " is not supported.");
- }
-
- nativesetenabledprotocols(ssl_op_no);
+ NativeCrypto.setEnabledProtocols(sslNativePointer, protocols);
}
- /**
- * Gets all available ciphers from the current OpenSSL library.
- * Needed by OpenSSLServerSocketFactory too.
- */
- static native String[] nativegetsupportedciphersuites();
-
@Override
public String[] getSupportedCipherSuites() {
- return nativegetsupportedciphersuites();
+ return NativeCrypto.getSupportedCipherSuites();
}
@Override
public String[] getEnabledCipherSuites() {
- return OpenSSLSocketImpl.nativeGetEnabledCipherSuites(ssl_ctx);
+ return NativeCrypto.SSL_get_ciphers(sslNativePointer);
}
/**
@@ -211,7 +122,7 @@
*/
@Override
public void setEnabledCipherSuites(String[] suites) {
- OpenSSLSocketImpl.setEnabledCipherSuites(ssl_ctx, suites);
+ NativeCrypto.setEnabledCipherSuites(sslNativePointer, suites);
}
/**
@@ -223,10 +134,10 @@
static private int SSL_VERIFY_CLIENT_ONCE = 0x04;
/**
- * Calls the SSL_CTX_set_verify(...) OpenSSL function with the passed int
+ * Calls the SSL_set_verify(...) OpenSSL function with the passed int
* value.
*/
- private native void nativesetclientauth(int value);
+ private static native void nativesetclientauth(int sslNativePointer, int value);
private void setClientAuth() {
int value = SSL_VERIFY_NONE;
@@ -237,7 +148,7 @@
value |= SSL_VERIFY_PEER|SSL_VERIFY_CLIENT_ONCE;
}
- nativesetclientauth(value);
+ nativesetclientauth(sslNativePointer, value);
}
@Override
@@ -274,20 +185,13 @@
@Override
public Socket accept() throws IOException {
- OpenSSLSocketImpl socket
- = new OpenSSLSocketImpl(sslParameters, ssl_op_no);
+ OpenSSLSocketImpl socket = new OpenSSLSocketImpl(sslParameters, null);
implAccept(socket);
- socket.accept(ssl_ctx, client_mode);
-
+ socket.accept(sslNativePointer);
return socket;
}
/**
- * Removes OpenSSL objects from memory.
- */
- private native void nativefree();
-
- /**
* Unbinds the port if the socket is open.
*/
@Override
@@ -297,7 +201,10 @@
@Override
public synchronized void close() throws IOException {
- nativefree();
+ if (sslNativePointer != 0) {
+ NativeCrypto.SSL_free(sslNativePointer);
+ sslNativePointer = 0;
+ }
super.close();
}
}
diff --git a/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl.java b/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl.java
index 17cd088..2a3908c 100644
--- a/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl.java
+++ b/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl.java
@@ -41,7 +41,7 @@
* Implementation of the class OpenSSLSessionImpl
* based on OpenSSL. The JNI native interface for some methods
* of this this class are defined in the file:
- * org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl.cpp
+ * org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp
*/
public class OpenSSLSessionImpl implements SSLSession {
@@ -52,11 +52,11 @@
private boolean isValid = true;
private TwoKeyHashMap values = new TwoKeyHashMap();
private javax.security.cert.X509Certificate[] peerCertificateChain;
- protected int session;
+ protected int sslSessionNativePointer;
private SSLParameters sslParameters;
private String peerHost;
private int peerPort;
- private final SSLSessionContext sessionContext;
+ private final AbstractSessionContext sessionContext;
/**
* Class constructor creates an SSL session context given the appropriate
@@ -65,9 +65,9 @@
* @param session the Identifier for SSL session
* @param sslParameters the SSL parameters like ciphers' suites etc.
*/
- protected OpenSSLSessionImpl(int session, SSLParameters sslParameters,
- String peerHost, int peerPort, SSLSessionContext sessionContext) {
- this.session = session;
+ protected OpenSSLSessionImpl(int sslSessionNativePointer, SSLParameters sslParameters,
+ String peerHost, int peerPort, AbstractSessionContext sessionContext) {
+ this.sslSessionNativePointer = sslSessionNativePointer;
this.sslParameters = sslParameters;
this.peerHost = peerHost;
this.peerPort = peerPort;
@@ -75,51 +75,58 @@
}
/**
- * Constructs a session from a byte[].
+ * Constructs a session from a byte[] containing DER data. This
+ * allows loading the saved session.
+ * @throws IOException
*/
OpenSSLSessionImpl(byte[] derData, SSLParameters sslParameters,
String peerHost, int peerPort,
javax.security.cert.X509Certificate[] peerCertificateChain,
- SSLSessionContext sessionContext)
+ AbstractSessionContext sessionContext)
throws IOException {
- this.sslParameters = sslParameters;
- this.peerHost = peerHost;
- this.peerPort = peerPort;
+ this(initializeNativeImpl(derData, derData.length),
+ sslParameters,
+ peerHost,
+ peerPort,
+ sessionContext);
this.peerCertificateChain = peerCertificateChain;
- this.sessionContext = sessionContext;
- initializeNative(derData);
+ // TODO move this check into native code so we can throw an error with more information
+ if (this.sslSessionNativePointer == 0) {
+ throw new IOException("Invalid session data");
+ }
}
+ private static native int initializeNativeImpl(byte[] data, int size);
+
/**
* Gets the identifier of the actual SSL session
* @return array of sessions' identifiers.
*/
- public native byte[] getId();
+ public byte[] getId() {
+ return getId(sslSessionNativePointer);
+ }
+
+ private static native byte[] getId(int sslSessionNativePointer);
/**
* Get the session object in DER format. This allows saving the session
* data or sharing it with other processes.
*/
- native byte[] getEncoded();
-
- /**
- * Init the underlying native object from DER data. This
- * allows loading the saved session.
- * @throws IOException
- */
- private void initializeNative(byte[] derData) throws IOException {
- this.session = initializeNativeImpl(derData, derData.length);
- if (this.session == 0) {
- throw new IOException("Invalid session data");
- }
+ byte[] getEncoded() {
+ return getEncoded(sslSessionNativePointer);
}
- private native int initializeNativeImpl(byte[] data, int size);
-
+
+ private native static byte[] getEncoded(int sslSessionNativePointer);
+
/**
* Gets the creation time of the SSL session.
* @return the session's creation time in milliseconds since the epoch
*/
- public native long getCreationTime();
+ public long getCreationTime() {
+ return getCreationTime(sslSessionNativePointer);
+ }
+
+ private static native long getCreationTime(int sslSessionNativePointer);
/**
* Gives the last time this concrete SSL session was accessed. Accessing
@@ -184,7 +191,8 @@
/**
* Returns the X509 certificates of the peer in the PEM format.
*/
- private native byte[][] getPeerCertificatesImpl();
+ private static native byte[][] getPeerCertificatesImpl(int sslCtxNativePointer,
+ int sslSessionNativePointer);
/**
* Gives the certificate(s) of the peer in this SSL session
@@ -201,7 +209,7 @@
public javax.security.cert.X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException {
if (peerCertificateChain == null) {
try {
- byte[][] bytes = getPeerCertificatesImpl();
+ byte[][] bytes = getPeerCertificatesImpl(sessionContext.sslCtxNativePointer, sslSessionNativePointer);
if (bytes == null) throw new SSLPeerUnverifiedException("No certificate available");
peerCertificateChain = new javax.security.cert.X509Certificate[bytes.length];
@@ -303,7 +311,11 @@
* @return an identifier for all the cryptographic algorithms used in the
* actual SSL session.
*/
- public native String getCipherSuite();
+ public String getCipherSuite() {
+ return getCipherSuite(sslSessionNativePointer);
+ }
+
+ private static native String getCipherSuite(int sslSessionNativePointer);
/**
* Gives back the standard version name of the SSL protocol used in all
@@ -313,7 +325,11 @@
* connections pertaining to this SSL session.
*
*/
- public native String getProtocol();
+ public String getProtocol() {
+ return getProtocol(sslSessionNativePointer);
+ }
+
+ private static native String getProtocol(int sslSessionNativePointer);
/**
* Gives back the context to which the actual SSL session is bound. A SSL
@@ -457,10 +473,8 @@
}
protected void finalize() {
- synchronized (OpenSSLSocketImpl.class) {
- freeImpl(session);
- }
+ freeImpl(sslSessionNativePointer);
}
- private native void freeImpl(int session);
+ private static native void freeImpl(int session);
}
diff --git a/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketFactoryImpl.java b/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketFactoryImpl.java
index aeb23b6..7b6d7c8 100644
--- a/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketFactoryImpl.java
+++ b/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketFactoryImpl.java
@@ -46,12 +46,11 @@
}
public String[] getDefaultCipherSuites() {
- // TODO There might be a better implementation for this...
- return OpenSSLSocketImpl.nativegetsupportedciphersuites();
+ return NativeCrypto.getDefaultCipherSuites();
}
public String[] getSupportedCipherSuites() {
- return OpenSSLSocketImpl.nativegetsupportedciphersuites();
+ return NativeCrypto.getSupportedCipherSuites();
}
public Socket createSocket() throws IOException {
diff --git a/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java b/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java
index 8006757..58d2110 100644
--- a/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java
+++ b/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java
@@ -16,7 +16,6 @@
package org.apache.harmony.xnet.provider.jsse;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -25,7 +24,6 @@
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
-import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPublicKey;
@@ -41,43 +39,37 @@
import javax.net.ssl.SSLSession;
import org.apache.harmony.security.provider.cert.X509CertImpl;
-import org.bouncycastle.openssl.PEMWriter;
/**
* Implementation of the class OpenSSLSocketImpl
* based on OpenSSL. The JNI native interface for some methods
* of this this class are defined in the file:
- * org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl.cpp
- *
+ * org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp
+ *
* This class only supports SSLv3 and TLSv1. This should be documented elsewhere
- * later, for example in the package.html or a separate reference document.
+ * later, for example in the package.html or a separate reference document.
*/
-public class OpenSSLSocketImpl extends javax.net.ssl.SSLSocket {
- private int ssl_ctx;
- private int ssl;
+public class OpenSSLSocketImpl
+ extends javax.net.ssl.SSLSocket
+ implements NativeCrypto.CertificateChainVerifier {
+ private int sslNativePointer;
private InputStream is;
private OutputStream os;
private final Object handshakeLock = new Object();
- private Object readLock = new Object();
- private Object writeLock = new Object();
+ private final Object readLock = new Object();
+ private final Object writeLock = new Object();
private SSLParameters sslParameters;
private OpenSSLSessionImpl sslSession;
private Socket socket;
private boolean autoClose;
private boolean handshakeStarted = false;
private ArrayList<HandshakeCompletedListener> listeners;
- private long ssl_op_no = 0x00000000L;
private int timeout = 0;
// BEGIN android-added
private int handshakeTimeout = -1; // -1 = same as timeout; 0 = infinite
// END android-added
private InetSocketAddress address;
- private static final String[] supportedProtocols = new String[] {
- "SSLv3",
- "TLSv1"
- };
-
private static final AtomicInteger instanceCount = new AtomicInteger(0);
public static int getInstanceCount() {
@@ -89,66 +81,6 @@
}
/**
- * Initialize OpenSSL library.
- */
- private native static void nativeinitstatic();
-
- static {
- nativeinitstatic();
- }
-
- private native void nativeinit(String privatekey, String certificate, byte[] seed);
-
- /**
- * Initialize the SSL socket and set the certificates for the
- * future handshaking.
- */
- private void init() throws IOException {
- String alias = sslParameters.getKeyManager().chooseClientAlias(new String[] { "RSA" }, null, null);
- if (alias != null) {
- PrivateKey privateKey = sslParameters.getKeyManager().getPrivateKey(alias);
- X509Certificate[] certificates = sslParameters.getKeyManager().getCertificateChain(alias);
-
- ByteArrayOutputStream privateKeyOS = new ByteArrayOutputStream();
- PEMWriter privateKeyPEMWriter = new PEMWriter(new OutputStreamWriter(privateKeyOS));
- privateKeyPEMWriter.writeObject(privateKey);
- privateKeyPEMWriter.close();
-
- ByteArrayOutputStream certificateOS = new ByteArrayOutputStream();
- PEMWriter certificateWriter = new PEMWriter(new OutputStreamWriter(certificateOS));
-
- for (int i = 0; i < certificates.length; i++) {
- certificateWriter.writeObject(certificates[i]);
- }
- certificateWriter.close();
-
- nativeinit(privateKeyOS.toString(), certificateOS.toString(),
- sslParameters.getSecureRandomMember() != null ?
- sslParameters.getSecureRandomMember().generateSeed(1024) : null);
- } else {
- nativeinit(null, null,
- sslParameters.getSecureRandomMember() != null ?
- sslParameters.getSecureRandomMember().generateSeed(1024) : null);
- }
- }
-
- /**
- * Class constructor with 2 parameters
- *
- * @param sslParameters Parameters for the SSL
- * context
- * @param ssl_op_no Parameter to set the enabled
- * protocols
- * @throws IOException if network fails
- */
- protected OpenSSLSocketImpl(SSLParameters sslParameters, long ssl_op_no) throws IOException {
- super();
- this.sslParameters = sslParameters;
- this.ssl_op_no = ssl_op_no;
- updateInstanceCount(1);
- }
-
- /**
* Class constructor with 1 parameter
*
* @param sslParameters Parameters for the SSL
@@ -157,9 +89,7 @@
*/
protected OpenSSLSocketImpl(SSLParameters sslParameters) throws IOException {
super();
- this.sslParameters = sslParameters;
- init();
- updateInstanceCount(1);
+ init(sslParameters);
}
/**
@@ -172,12 +102,9 @@
SSLParameters sslParameters)
throws IOException {
super(host, port);
- this.sslParameters = sslParameters;
- init();
- updateInstanceCount(1);
+ init(sslParameters);
}
-
/**
* Class constructor with 3 parameters: 1st is InetAddress
*
@@ -188,9 +115,7 @@
SSLParameters sslParameters)
throws IOException {
super(address, port);
- this.sslParameters = sslParameters;
- init();
- updateInstanceCount(1);
+ init(sslParameters);
}
@@ -204,9 +129,7 @@
int clientPort, SSLParameters sslParameters)
throws IOException {
super(host, port, clientAddress, clientPort);
- this.sslParameters = sslParameters;
- init();
- updateInstanceCount(1);
+ init(sslParameters);
}
/**
@@ -219,9 +142,7 @@
InetAddress clientAddress, int clientPort, SSLParameters sslParameters)
throws IOException {
super(address, port, clientAddress, clientPort);
- this.sslParameters = sslParameters;
- init();
- updateInstanceCount(1);
+ init(sslParameters);
}
/**
@@ -237,8 +158,28 @@
this.timeout = socket.getSoTimeout();
this.address = new InetSocketAddress(host, port);
this.autoClose = autoClose;
+ init(sslParameters);
+ }
+
+ /**
+ * Initialize the SSL socket and set the certificates for the
+ * future handshaking.
+ */
+ private void init(SSLParameters sslParameters) throws IOException {
this.sslParameters = sslParameters;
- init();
+ this.sslNativePointer = NativeCrypto.SSL_new(sslParameters);
+ updateInstanceCount(1);
+ }
+
+ /**
+ * Construct a OpenSSLSocketImpl from an SSLParameters and an
+ * existing SSL native pointer. Used for transitioning accepting
+ * the OpenSSLSocketImpl within OpenSSLServerSocketImpl.
+ */
+ protected OpenSSLSocketImpl(SSLParameters sslParameters,
+ OpenSSLServerSocketImpl dummy) {
+ super();
+ this.sslParameters = (SSLParameters) sslParameters.clone();
updateInstanceCount(1);
}
@@ -246,9 +187,9 @@
* Adds OpenSSL functionality to the existing socket and starts the SSL
* handshaking.
*/
- private native boolean nativeconnect(int ctx, Socket sock, boolean client_mode, int sslsession) throws IOException;
- private native int nativegetsslsession(int ssl);
- private native String nativecipherauthenticationmethod();
+ private native boolean nativeconnect(int sslNativePointer, Socket sock, int timeout, boolean client_mode, int sslSessionNativePointer) throws IOException;
+ private native int nativegetsslsession(int sslNativePointer);
+ private native String nativecipherauthenticationmethod(int sslNativePointer);
/**
* Gets the suitable session reference from the session cache container.
@@ -256,6 +197,9 @@
* @return OpenSSLSessionImpl
*/
private OpenSSLSessionImpl getCachedClientSession() {
+ if (!sslParameters.getUseClientMode()) {
+ return null;
+ }
if (super.getInetAddress() == null ||
super.getInetAddress().getHostAddress() == null ||
super.getInetAddress().getHostName() == null) {
@@ -273,8 +217,7 @@
* before logging is ready.
*/
static class LoggerHolder {
- static final Logger logger = Logger.getLogger(
- OpenSSLSocketImpl.class.getName());
+ static final Logger logger = Logger.getLogger(OpenSSLSocketImpl.class.getName());
}
/**
@@ -282,7 +225,7 @@
* from the OpenSSL library. It can negotiate new encryption keys, change
* cipher suites, or initiate a new session. The certificate chain is
* verified if the correspondent property in java.Security is set. All
- * listensers are notified at the end of the TLS/SSL handshake.
+ * listeners are notified at the end of the TLS/SSL handshake.
*
* @throws <code>IOException</code> if network fails
*/
@@ -300,86 +243,77 @@
// Check if it's allowed to create a new session (default is true)
if (session == null && !sslParameters.getEnableSessionCreation()) {
throw new SSLHandshakeException("SSL Session may not be created");
- } else {
- // BEGIN android-added
- // Temporarily use a different timeout for the handshake process
- int savedTimeout = timeout;
- if (handshakeTimeout >= 0) {
- setSoTimeout(handshakeTimeout);
- }
- // END android-added
-
- Socket socket = this.socket != null ? this.socket : this;
- int sessionId = session != null ? session.session : 0;
- boolean reusedSession;
- synchronized (OpenSSLSocketImpl.class) {
- reusedSession = nativeconnect(ssl_ctx, socket,
- sslParameters.getUseClientMode(), sessionId);
- }
- if (reusedSession) {
- // nativeconnect shouldn't return true if the session is not
- // done
- session.lastAccessedTime = System.currentTimeMillis();
- sslSession = session;
-
- LoggerHolder.logger.fine("Reused cached session for "
- + getInetAddress().getHostName() + ".");
- } else {
- if (session != null) {
- LoggerHolder.logger.fine("Reuse of cached session for "
- + getInetAddress().getHostName() + " failed.");
- } else {
- LoggerHolder.logger.fine("Created new session for "
- + getInetAddress().getHostName() + ".");
- }
-
- ClientSessionContext sessionContext
- = sslParameters.getClientSessionContext();
- synchronized (OpenSSLSocketImpl.class) {
- sessionId = nativegetsslsession(ssl);
- }
- if (address == null) {
- sslSession = new OpenSSLSessionImpl(
- sessionId, sslParameters,
- super.getInetAddress().getHostName(),
- super.getPort(), sessionContext);
- } else {
- sslSession = new OpenSSLSessionImpl(
- sessionId, sslParameters,
- address.getHostName(), address.getPort(),
- sessionContext);
- }
-
- try {
- X509Certificate[] peerCertificates = (X509Certificate[])
- sslSession.getPeerCertificates();
-
- if (peerCertificates == null
- || peerCertificates.length == 0) {
- throw new SSLException("Server sends no certificate");
- }
-
- String authMethod;
- synchronized (OpenSSLSocketImpl.class) {
- authMethod = nativecipherauthenticationmethod();
- }
- sslParameters.getTrustManager().checkServerTrusted(
- peerCertificates,
- authMethod);
- sessionContext.putSession(sslSession);
- } catch (CertificateException e) {
- throw new SSLException("Not trusted server certificate", e);
- }
- }
-
- // BEGIN android-added
- // Restore the original timeout now that the handshake is complete
- if (handshakeTimeout >= 0) {
- setSoTimeout(savedTimeout);
- }
- // END android-added
}
+ // BEGIN android-added
+ // Temporarily use a different timeout for the handshake process
+ int savedTimeout = timeout;
+ if (handshakeTimeout >= 0) {
+ setSoTimeout(handshakeTimeout);
+ }
+ // END android-added
+
+ Socket socket = this.socket != null ? this.socket : this;
+ int sslSessionNativePointer = session != null ? session.sslSessionNativePointer : 0;
+ boolean reusedSession = nativeconnect(sslNativePointer, socket, timeout,
+ sslParameters.getUseClientMode(), sslSessionNativePointer);
+ if (reusedSession) {
+ // nativeconnect shouldn't return true if the session is not
+ // done
+ session.lastAccessedTime = System.currentTimeMillis();
+ sslSession = session;
+
+ LoggerHolder.logger.fine("Reused cached session for "
+ + getInetAddress().getHostName() + ".");
+ } else {
+ if (session != null) {
+ LoggerHolder.logger.fine("Reuse of cached session for "
+ + getInetAddress().getHostName() + " failed.");
+ } else {
+ LoggerHolder.logger.fine("Created new session for "
+ + getInetAddress().getHostName() + ".");
+ }
+
+ AbstractSessionContext sessionContext =
+ (sslParameters.getUseClientMode()) ?
+ sslParameters.getClientSessionContext() :
+ sslParameters.getServerSessionContext();
+ sslSessionNativePointer = nativegetsslsession(sslNativePointer);
+ if (address == null) {
+ sslSession = new OpenSSLSessionImpl(sslSessionNativePointer, sslParameters,
+ super.getInetAddress().getHostName(),
+ super.getPort(), sessionContext);
+ } else {
+ sslSession = new OpenSSLSessionImpl(sslSessionNativePointer, sslParameters,
+ address.getHostName(), address.getPort(),
+ sessionContext);
+ }
+
+ try {
+ X509Certificate[] peerCertificates = (X509Certificate[])
+ sslSession.getPeerCertificates();
+
+ if (peerCertificates == null
+ || peerCertificates.length == 0) {
+ throw new SSLException("Server sends no certificate");
+ }
+
+ String authMethod = nativecipherauthenticationmethod(sslNativePointer);
+ sslParameters.getTrustManager().checkServerTrusted(peerCertificates,
+ authMethod);
+ sessionContext.putSession(sslSession);
+ } catch (CertificateException e) {
+ throw new SSLException("Not trusted server certificate", e);
+ }
+ }
+
+ // BEGIN android-added
+ // Restore the original timeout now that the handshake is complete
+ if (handshakeTimeout >= 0) {
+ setSoTimeout(savedTimeout);
+ }
+ // END android-added
+
if (listeners != null) {
// notify the listeners
HandshakeCompletedEvent event =
@@ -392,22 +326,22 @@
}
// To be synchronized because of the verify_callback
- native synchronized void nativeaccept(Socket socketObject, int m_ctx, boolean client_mode);
+ native synchronized int nativeaccept(int sslNativePointer, Socket socketObject);
/**
* Performs the first part of a SSL/TLS handshaking process with a given
* 'host' connection and initializes the SSLSession.
*/
- protected void accept(int m_ctx, boolean client_mode) throws IOException {
+ protected void accept(int serverSslNativePointer) throws IOException {
// Must be set because no handshaking is necessary
// in this situation
handshakeStarted = true;
- nativeaccept(this, m_ctx, client_mode);
+ sslNativePointer = nativeaccept(serverSslNativePointer, this);
ServerSessionContext sessionContext
= sslParameters.getServerSessionContext();
- sslSession = new OpenSSLSessionImpl(nativegetsslsession(ssl),
+ sslSession = new OpenSSLSessionImpl(nativegetsslsession(sslNativePointer),
sslParameters, super.getInetAddress().getHostName(),
super.getPort(), sessionContext);
sslSession.lastAccessedTime = System.currentTimeMillis();
@@ -415,14 +349,16 @@
}
/**
- * Callback methode for the OpenSSL native certificate verification process.
+ * Implementation of NativeCrypto.CertificateChainVerifier.
+ *
+ * Callback method for the OpenSSL native certificate verification process.
*
* @param bytes Byte array containing the cert's
* information.
- * @return 0 if the certificate verification fails or 1 if OK
+ * @return false if the certificate verification fails or true if OK
*/
@SuppressWarnings("unused")
- private int verify_callback(byte[][] bytes) {
+ public boolean verifyCertificateChain(byte[][] bytes) {
try {
X509Certificate[] peerCertificateChain
= new X509Certificate[bytes.length];
@@ -439,11 +375,13 @@
new SSLException("Not trusted server certificate", e));
}
} catch (javax.security.cert.CertificateException e) {
- return 0;
+ // TODO throw in all cases for consistency
+ return false;
} catch (IOException e) {
- return 0;
+ // TODO throw in all cases for consistency
+ return false;
}
- return 1;
+ return true;
}
/**
@@ -483,28 +421,28 @@
}
}
- /**
- * This method is not supported for this SSLSocket implementation.
- */
public void shutdownInput() throws IOException {
- throw new UnsupportedOperationException(
- "Method shutdownInput() is not supported.");
+ if (socket == null) {
+ super.shutdownInput();
+ return;
+ }
+ socket.shutdownInput();
}
- /**
- * This method is not supported for this SSLSocket implementation.
- */
public void shutdownOutput() throws IOException {
- throw new UnsupportedOperationException(
- "Method shutdownOutput() is not supported.");
+ if (socket == null) {
+ super.shutdownOutput();
+ return;
+ }
+ socket.shutdownOutput();
}
/**
* Reads with the native SSL_read function from the encrypted data stream
* @return -1 if error or the end of the stream is reached.
*/
- private native int nativeread(int timeout) throws IOException;
- private native int nativeread(byte[] b, int off, int len, int timeout) throws IOException;
+ private native int nativeread(int sslNativePointer, int timeout) throws IOException;
+ private native int nativeread(int sslNativePointer, byte[] b, int off, int len, int timeout) throws IOException;
/**
* This inner class provides input data stream functionality
@@ -529,7 +467,7 @@
*/
public int read() throws IOException {
synchronized(readLock) {
- return OpenSSLSocketImpl.this.nativeread(timeout);
+ return OpenSSLSocketImpl.this.nativeread(sslNativePointer, timeout);
}
}
@@ -539,7 +477,7 @@
*/
public int read(byte[] b, int off, int len) throws IOException {
synchronized(readLock) {
- return OpenSSLSocketImpl.this.nativeread(b, off, len, timeout);
+ return OpenSSLSocketImpl.this.nativeread(sslNativePointer, b, off, len, timeout);
}
}
}
@@ -547,8 +485,8 @@
/**
* Writes with the native SSL_write function to the encrypted data stream.
*/
- private native void nativewrite(int b) throws IOException;
- private native void nativewrite(byte[] b, int off, int len) throws IOException;
+ private native void nativewrite(int sslNativePointer, int b) throws IOException;
+ private native void nativewrite(int sslNativePointer, byte[] b, int off, int len) throws IOException;
/**
* This inner class provides output data stream functionality
@@ -559,7 +497,7 @@
SSLOutputStream() throws IOException {
/**
/* Note: When startHandshake() throws an exception, no
- * SSLInputStream object will be created.
+ * SSLOutputStream object will be created.
*/
OpenSSLSocketImpl.this.startHandshake();
}
@@ -570,7 +508,7 @@
*/
public void write(int b) throws IOException {
synchronized(writeLock) {
- OpenSSLSocketImpl.this.nativewrite(b);
+ OpenSSLSocketImpl.this.nativewrite(sslNativePointer, b);
}
}
@@ -580,7 +518,7 @@
*/
public void write(byte[] b, int start, int len) throws IOException {
synchronized(writeLock) {
- OpenSSLSocketImpl.this.nativewrite(b, start, len);
+ OpenSSLSocketImpl.this.nativewrite(sslNativePointer, b, start, len);
}
}
}
@@ -598,9 +536,6 @@
try {
startHandshake();
} catch (IOException e) {
- Logger.getLogger(getClass().getName()).log(Level.WARNING,
- "Error negotiating SSL connection.", e);
-
// return an invalid session with
// invalid cipher suite of "SSL_NULL_WITH_NULL_NULL"
return SSLSessionImpl.NULL_SESSION;
@@ -666,22 +601,14 @@
}
/**
- * Gets all available ciphers from the current OpenSSL library.
- * Needed by OpenSSLSocketFactory too.
- */
- static native String[] nativegetsupportedciphersuites();
-
- /**
* The names of the cipher suites which could be used by the SSL connection
* are returned.
* @return an array of cipher suite names
*/
public String[] getSupportedCipherSuites() {
- return nativegetsupportedciphersuites();
+ return NativeCrypto.getSupportedCipherSuites();
}
- static native String[] nativeGetEnabledCipherSuites(int ssl_ctx);
-
/**
* The names of the cipher suites that are in use in the actual the SSL
* connection are returned.
@@ -689,23 +616,7 @@
* @return an array of cipher suite names
*/
public String[] getEnabledCipherSuites() {
- return nativeGetEnabledCipherSuites(ssl_ctx);
- }
-
- /**
- * Calls the SSL_CTX_set_cipher_list(...) OpenSSL function with the passed
- * char array.
- */
- static native void nativeSetEnabledCipherSuites(int ssl_ctx, String controlString);
-
- private static boolean findSuite(String suite) {
- String[] supportedCipherSuites = nativegetsupportedciphersuites();
- for(int i = 0; i < supportedCipherSuites.length; i++) {
- if (supportedCipherSuites[i].equals(suite)) {
- return true;
- }
- }
- throw new IllegalArgumentException("Protocol " + suite + " is not supported.");
+ return NativeCrypto.SSL_get_ciphers(sslNativePointer);
}
/**
@@ -719,20 +630,7 @@
* is null.
*/
public void setEnabledCipherSuites(String[] suites) {
- setEnabledCipherSuites(ssl_ctx, suites);
- }
-
- static void setEnabledCipherSuites(int ssl_ctx, String[] suites) {
- if (suites == null) {
- throw new IllegalArgumentException("Provided parameter is null");
- }
- String controlString = "";
- for (int i = 0; i < suites.length; i++) {
- findSuite(suites[i]);
- if (i == 0) controlString = suites[i];
- else controlString += ":" + suites[i];
- }
- nativeSetEnabledCipherSuites(ssl_ctx, controlString);
+ NativeCrypto.setEnabledCipherSuites(sslNativePointer, suites);
}
/**
@@ -741,65 +639,32 @@
* @return an array of protocols names
*/
public String[] getSupportedProtocols() {
- return supportedProtocols.clone();
+ return NativeCrypto.getSupportedProtocols();
}
/**
- * SSL mode of operation with or without back compatibility. See the OpenSSL
- * ssl.h header file for more information.
- */
- static private long SSL_OP_NO_SSLv3 = 0x02000000L;
- static private long SSL_OP_NO_TLSv1 = 0x04000000L;
-
- /**
* The names of the protocols' versions that are in use on this SSL
* connection.
- *
+ *
* @return an array of protocols names
*/
@Override
public String[] getEnabledProtocols() {
- ArrayList<String> array = new ArrayList<String>();
-
- if ((ssl_op_no & SSL_OP_NO_SSLv3) == 0x00000000L) {
- array.add(supportedProtocols[1]);
- }
- if ((ssl_op_no & SSL_OP_NO_TLSv1) == 0x00000000L) {
- array.add(supportedProtocols[2]);
- }
- return array.toArray(new String[array.size()]);
+ return NativeCrypto.getEnabledProtocols(sslNativePointer);
}
- private native void nativesetenabledprotocols(long l);
-
/**
* This method enables the protocols' versions listed by
* getSupportedProtocols().
- *
+ *
* @param protocols The names of all the protocols to put on use
- *
+ *
* @throws IllegalArgumentException when one or more of the names in the
* array are not supported, or when the array is null.
*/
@Override
public synchronized void setEnabledProtocols(String[] protocols) {
-
- if (protocols == null) {
- throw new IllegalArgumentException("Provided parameter is null");
- }
-
- ssl_op_no = SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1;
-
- for(int i = 0; i < protocols.length; i++) {
- if (protocols[i].equals("SSLv3"))
- ssl_op_no ^= SSL_OP_NO_SSLv3;
- else if (protocols[i].equals("TLSv1"))
- ssl_op_no ^= SSL_OP_NO_TLSv1;
- else throw new IllegalArgumentException("Protocol " + protocols[i] +
- " is not supported.");
- }
-
- nativesetenabledprotocols(ssl_op_no);
+ NativeCrypto.setEnabledProtocols(sslNativePointer, protocols);
}
/**
@@ -912,8 +777,8 @@
}
// END android-added
- private native void nativeinterrupt() throws IOException;
- private native void nativeclose() throws IOException;
+ private native void nativeinterrupt(int sslNativePointer) throws IOException;
+ private native void nativeclose(int sslNativePointer) throws IOException;
/**
* Closes the SSL socket. Once closed, a socket is not available for further
@@ -929,9 +794,9 @@
synchronized (handshakeLock) {
if (!handshakeStarted) {
handshakeStarted = true;
-
+
synchronized (this) {
- nativefree();
+ free();
if (socket != null) {
if (autoClose && !socket.isClosed()) socket.close();
@@ -939,12 +804,12 @@
if (!super.isClosed()) super.close();
}
}
-
+
return;
}
}
- nativeinterrupt();
+ nativeinterrupt(sslNativePointer);
synchronized (this) {
synchronized (writeLock) {
@@ -955,7 +820,7 @@
// Shut down the SSL connection, per se.
try {
if (handshakeStarted) {
- nativeclose();
+ nativeclose(sslNativePointer);
}
} catch (IOException ex) {
/*
@@ -970,7 +835,7 @@
* the native structs, and we need to do so lest we leak
* memory.
*/
- nativefree();
+ free();
if (socket != null) {
if (autoClose && !socket.isClosed())
@@ -988,12 +853,18 @@
}
}
- private native void nativefree();
+ private void free() {
+ if (sslNativePointer == 0) {
+ return;
+ }
+ NativeCrypto.SSL_free(sslNativePointer);
+ sslNativePointer = 0;
+ }
protected void finalize() throws IOException {
updateInstanceCount(-1);
- if (ssl == 0) {
+ if (sslNativePointer == 0) {
/*
* It's already been closed, so there's no need to do anything
* more at this point.
diff --git a/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLBufferedInput.java b/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLBufferedInput.java
index a150470..31bb681 100644
--- a/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLBufferedInput.java
+++ b/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLBufferedInput.java
@@ -49,9 +49,6 @@
this.in = in;
}
- /**
- * Returns the number of bytes available for reading.
- */
@Override
public int available() throws IOException {
// in assumption that the buffer has been set
@@ -78,4 +75,3 @@
return bytik;
}
}
-
diff --git a/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLInputStream.java b/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLInputStream.java
index 507e14f..b2501a7 100644
--- a/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLInputStream.java
+++ b/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLInputStream.java
@@ -29,9 +29,6 @@
*/
public abstract class SSLInputStream extends InputStream {
- /**
- * @see java.io.InputStream#available()
- */
@Override
public abstract int available() throws IOException;
@@ -48,9 +45,6 @@
@Override
public abstract int read() throws IOException;
- /**
- * @see java.io.InputStream#skip(long)
- */
@Override
public long skip(long n) throws IOException {
long skept = n;
@@ -115,9 +109,6 @@
return res;
}
- /**
- * @see java.io.InputStream#read(byte[],int,int)
- */
@Override
public int read(byte[] b, int off, int len) throws IOException {
int read_b;
diff --git a/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLParameters.java b/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLParameters.java
index 89916de..525756d 100644
--- a/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLParameters.java
+++ b/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLParameters.java
@@ -93,29 +93,11 @@
if (enabledCipherSuites == null) this.enabledCipherSuites = CipherSuite.defaultCipherSuites;
return enabledCipherSuites;
}
-
- /**
- * Holds a pointer to our native SSL context.
- */
- private int ssl_ctx = 0;
-
- /**
- * Initializes our native SSL context.
- */
- private native int nativeinitsslctx();
-
- /**
- * Returns the native SSL context, creating it on-the-fly, if necessary.
- */
- protected synchronized int getSSLCTX() {
- if (ssl_ctx == 0) ssl_ctx = nativeinitsslctx();
- return ssl_ctx;
- }
// END android-changed
/**
* Initializes the parameters. Naturally this constructor is used
- * in SSLContextImpl.engineInit method which dirrectly passes its
+ * in SSLContextImpl.engineInit method which dirrectly passes its
* parameters. In other words this constructor holds all
* the functionality provided by SSLContext.init method.
* See {@link javax.net.ssl.SSLContext#init(KeyManager[],TrustManager[],
@@ -127,21 +109,21 @@
SSLServerSessionCache serverCache)
throws KeyManagementException {
this.serverSessionContext
- = new ServerSessionContext(this, serverCache);
+ = new ServerSessionContext(this, NativeCrypto.SSL_CTX_new(), serverCache);
this.clientSessionContext
- = new ClientSessionContext(this, clientCache);
+ = new ClientSessionContext(this, NativeCrypto.SSL_CTX_new(), clientCache);
// END android-changed
try {
// initialize key manager
boolean initialize_default = false;
- // It's not described by the spec of SSLContext what should happen
+ // It's not described by the spec of SSLContext what should happen
// if the arrays of length 0 are specified. This implementation
// behave as for null arrays (i.e. use installed security providers)
if ((kms == null) || (kms.length == 0)) {
if (defaultKeyManager == null) {
KeyManagerFactory kmf = KeyManagerFactory.getInstance(
KeyManagerFactory.getDefaultAlgorithm());
- kmf.init(null, null);
+ kmf.init(null, null);
kms = kmf.getKeyManagers();
// tell that we are trying to initialize defaultKeyManager
initialize_default = true;
diff --git a/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerSessionContext.java b/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerSessionContext.java
index a3c2c6d..c379fee 100644
--- a/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerSessionContext.java
+++ b/libcore/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerSessionContext.java
@@ -46,8 +46,9 @@
private final SSLServerSessionCache persistentCache;
public ServerSessionContext(SSLParameters parameters,
+ int sslCtxNativePointer,
SSLServerSessionCache persistentCache) {
- super(parameters, 100, 0);
+ super(parameters, sslCtxNativePointer, 100, 0);
this.persistentCache = persistentCache;
}
@@ -107,8 +108,13 @@
return null;
}
+ @Override
void putSession(SSLSession session) {
- ByteArray key = new ByteArray(session.getId());
+ byte[] id = session.getId();
+ if (id.length == 0) {
+ return;
+ }
+ ByteArray key = new ByteArray(id);
synchronized (sessions) {
sessions.put(key, session);
}
diff --git a/libcore/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp b/libcore/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp
index 3bfe061..cb275b7 100644
--- a/libcore/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp
+++ b/libcore/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp
@@ -46,7 +46,7 @@
/**
* Frees the SSL error state.
- *
+ *
* OpenSSL keeps an "error stack" per thread, and given that this code
* can be called from arbitrary threads that we don't keep track of,
* we err on the side of freeing the error state promptly (instead of,
@@ -60,7 +60,7 @@
/*
* Checks this thread's OpenSSL error queue and throws a RuntimeException if
* necessary.
- *
+ *
* @return 1 if an exception was thrown, 0 if not.
*/
static int throwExceptionIfNecessary(JNIEnv* env) {
@@ -79,328 +79,6 @@
return result;
}
-/**
- * Converts a Java byte[] to an OpenSSL BIGNUM, allocating the BIGNUM on the
- * fly.
- */
-static BIGNUM* arrayToBignum(JNIEnv* env, jbyteArray source) {
- // LOGD("Entering arrayToBignum()");
-
- jbyte* sourceBytes = env->GetByteArrayElements(source, NULL);
- int sourceLength = env->GetArrayLength(source);
- BIGNUM* bignum = BN_bin2bn((unsigned char*) sourceBytes, sourceLength, NULL);
- env->ReleaseByteArrayElements(source, sourceBytes, JNI_ABORT);
- return bignum;
-}
-
-/**
- * private static native int EVP_PKEY_new_DSA(byte[] p, byte[] q, byte[] g, byte[] pub_key, byte[] priv_key);
- */
-static EVP_PKEY* NativeCrypto_EVP_PKEY_new_DSA(JNIEnv* env, jclass clazz, jbyteArray p, jbyteArray q, jbyteArray g, jbyteArray pub_key, jbyteArray priv_key) {
- // LOGD("Entering EVP_PKEY_new_DSA()");
-
- DSA* dsa = DSA_new();
-
- dsa->p = arrayToBignum(env, p);
- dsa->q = arrayToBignum(env, q);
- dsa->g = arrayToBignum(env, g);
- dsa->pub_key = arrayToBignum(env, pub_key);
-
- if (priv_key != NULL) {
- dsa->priv_key = arrayToBignum(env, priv_key);
- }
-
- if (dsa->p == NULL || dsa->q == NULL || dsa->g == NULL || dsa->pub_key == NULL) {
- DSA_free(dsa);
- jniThrowRuntimeException(env, "Unable to convert BigInteger to BIGNUM");
- return NULL;
- }
-
- EVP_PKEY* pkey = EVP_PKEY_new();
- EVP_PKEY_assign_DSA(pkey, dsa);
-
- return pkey;
-}
-
-/**
- * private static native int EVP_PKEY_new_RSA(byte[] n, byte[] e, byte[] d, byte[] p, byte[] q);
- */
-static EVP_PKEY* NativeCrypto_EVP_PKEY_new_RSA(JNIEnv* env, jclass clazz, jbyteArray n, jbyteArray e, jbyteArray d, jbyteArray p, jbyteArray q) {
- // LOGD("Entering EVP_PKEY_new_RSA()");
-
- RSA* rsa = RSA_new();
-
- rsa->n = arrayToBignum(env, n);
- rsa->e = arrayToBignum(env, e);
-
- if (d != NULL) {
- rsa->d = arrayToBignum(env, d);
- }
-
- if (p != NULL) {
- rsa->p = arrayToBignum(env, p);
- }
-
- if (q != NULL) {
- rsa->q = arrayToBignum(env, q);
- }
-
- // int check = RSA_check_key(rsa);
- // LOGI("RSA_check_key returns %d", check);
-
- if (rsa->n == NULL || rsa->e == NULL) {
- RSA_free(rsa);
- jniThrowRuntimeException(env, "Unable to convert BigInteger to BIGNUM");
- return NULL;
- }
-
- EVP_PKEY* pkey = EVP_PKEY_new();
- EVP_PKEY_assign_RSA(pkey, rsa);
-
- return pkey;
-}
-
-/**
- * private static native void EVP_PKEY_free(int pkey);
- */
-static void NativeCrypto_EVP_PKEY_free(JNIEnv* env, jclass clazz, EVP_PKEY* pkey) {
- // LOGD("Entering EVP_PKEY_free()");
-
- if (pkey != NULL) {
- EVP_PKEY_free(pkey);
- }
-}
-
-/*
- * public static native int EVP_new()
- */
-static jint NativeCrypto_EVP_new(JNIEnv* env, jclass clazz) {
- // LOGI("NativeCrypto_EVP_DigestNew");
-
- return (jint)EVP_MD_CTX_create();
-}
-
-/*
- * public static native void EVP_free(int)
- */
-static void NativeCrypto_EVP_free(JNIEnv* env, jclass clazz, EVP_MD_CTX* ctx) {
- // LOGI("NativeCrypto_EVP_DigestFree");
-
- if (ctx != NULL) {
- EVP_MD_CTX_destroy(ctx);
- }
-}
-
-/*
- * public static native int EVP_DigestFinal(int, byte[], int)
- */
-static jint NativeCrypto_EVP_DigestFinal(JNIEnv* env, jclass clazz, EVP_MD_CTX* ctx, jbyteArray hash, jint offset) {
- // LOGI("NativeCrypto_EVP_DigestFinal%x, %x, %d, %d", ctx, hash, offset);
-
- if (ctx == NULL || hash == NULL) {
- jniThrowNullPointerException(env, NULL);
- return -1;
- }
-
- int result = -1;
-
- jbyte* hashBytes = env->GetByteArrayElements(hash, NULL);
- EVP_DigestFinal(ctx, (unsigned char*) (hashBytes + offset), (unsigned int*)&result);
- env->ReleaseByteArrayElements(hash, hashBytes, 0);
-
- throwExceptionIfNecessary(env);
-
- return result;
-}
-
-/*
- * public static native void EVP_DigestInit(int, java.lang.String)
- */
-static void NativeCrypto_EVP_DigestInit(JNIEnv* env, jclass clazz, EVP_MD_CTX* ctx, jstring algorithm) {
- // LOGI("NativeCrypto_EVP_DigestInit");
-
- if (ctx == NULL || algorithm == NULL) {
- jniThrowNullPointerException(env, NULL);
- return;
- }
-
- const char* algorithmChars = env->GetStringUTFChars(algorithm, NULL);
-
- const EVP_MD *digest = EVP_get_digestbynid(OBJ_txt2nid(algorithmChars));
- env->ReleaseStringUTFChars(algorithm, algorithmChars);
-
- if (digest == NULL) {
- jniThrowRuntimeException(env, "Hash algorithm not found");
- return;
- }
-
- EVP_DigestInit(ctx, digest);
-
- throwExceptionIfNecessary(env);
-}
-
-/*
- * public static native void EVP_DigestSize(int)
- */
-static jint NativeCrypto_EVP_DigestSize(JNIEnv* env, jclass clazz, EVP_MD_CTX* ctx) {
- // LOGI("NativeCrypto_EVP_DigestSize");
-
- if (ctx == NULL) {
- jniThrowNullPointerException(env, NULL);
- return -1;
- }
-
- int result = EVP_MD_CTX_size(ctx);
-
- throwExceptionIfNecessary(env);
-
- return result;
-}
-
-/*
- * public static native void EVP_DigestBlockSize(int)
- */
-static jint NativeCrypto_EVP_DigestBlockSize(JNIEnv* env, jclass clazz, EVP_MD_CTX* ctx) {
- // LOGI("NativeCrypto_EVP_DigestBlockSize");
-
- if (ctx == NULL) {
- jniThrowNullPointerException(env, NULL);
- return -1;
- }
-
- int result = EVP_MD_CTX_block_size(ctx);
-
- throwExceptionIfNecessary(env);
-
- return result;
-}
-
-/*
- * public static native void EVP_DigestUpdate(int, byte[], int, int)
- */
-static void NativeCrypto_EVP_DigestUpdate(JNIEnv* env, jclass clazz, EVP_MD_CTX* ctx, jbyteArray buffer, jint offset, jint length) {
- // LOGI("NativeCrypto_EVP_DigestUpdate %x, %x, %d, %d", ctx, buffer, offset, length);
-
- if (ctx == NULL || buffer == NULL) {
- jniThrowNullPointerException(env, NULL);
- return;
- }
-
- jbyte* bufferBytes = env->GetByteArrayElements(buffer, NULL);
- EVP_DigestUpdate(ctx, (unsigned char*) (bufferBytes + offset), length);
- env->ReleaseByteArrayElements(buffer, bufferBytes, JNI_ABORT);
-
- throwExceptionIfNecessary(env);
-}
-
-/*
- * public static native void EVP_VerifyInit(int, java.lang.String)
- */
-static void NativeCrypto_EVP_VerifyInit(JNIEnv* env, jclass clazz, EVP_MD_CTX* ctx, jstring algorithm) {
- // LOGI("NativeCrypto_EVP_VerifyInit");
-
- if (ctx == NULL || algorithm == NULL) {
- jniThrowNullPointerException(env, NULL);
- return;
- }
-
- const char* algorithmChars = env->GetStringUTFChars(algorithm, NULL);
-
- const EVP_MD *digest = EVP_get_digestbynid(OBJ_txt2nid(algorithmChars));
- env->ReleaseStringUTFChars(algorithm, algorithmChars);
-
- if (digest == NULL) {
- jniThrowRuntimeException(env, "Hash algorithm not found");
- return;
- }
-
- EVP_VerifyInit(ctx, digest);
-
- throwExceptionIfNecessary(env);
-}
-
-/*
- * public static native void EVP_VerifyUpdate(int, byte[], int, int)
- */
-static void NativeCrypto_EVP_VerifyUpdate(JNIEnv* env, jclass clazz, EVP_MD_CTX* ctx, jbyteArray buffer, jint offset, jint length) {
- // LOGI("NativeCrypto_EVP_VerifyUpdate %x, %x, %d, %d", ctx, buffer, offset, length);
-
- if (ctx == NULL || buffer == NULL) {
- jniThrowNullPointerException(env, NULL);
- return;
- }
-
- jbyte* bufferBytes = env->GetByteArrayElements(buffer, NULL);
- EVP_VerifyUpdate(ctx, (unsigned char*) (bufferBytes + offset), length);
- env->ReleaseByteArrayElements(buffer, bufferBytes, JNI_ABORT);
-
- throwExceptionIfNecessary(env);
-}
-
-/*
- * public static native void EVP_VerifyFinal(int, byte[], int, int, int)
- */
-static int NativeCrypto_EVP_VerifyFinal(JNIEnv* env, jclass clazz, EVP_MD_CTX* ctx, jbyteArray buffer, jint offset, jint length, EVP_PKEY* pkey) {
- // LOGI("NativeCrypto_EVP_VerifyFinal %x, %x, %d, %d %x", ctx, buffer, offset, length, pkey);
-
- if (ctx == NULL || buffer == NULL || pkey == NULL) {
- jniThrowNullPointerException(env, NULL);
- return -1;
- }
-
- jbyte* bufferBytes = env->GetByteArrayElements(buffer, NULL);
- int result = EVP_VerifyFinal(ctx, (unsigned char*) (bufferBytes + offset), length, pkey);
- env->ReleaseByteArrayElements(buffer, bufferBytes, JNI_ABORT);
-
- throwExceptionIfNecessary(env);
-
- return result;
-}
-
-/*
- * Defines the mapping from Java methods and their signatures
- * to native functions. Order is (1) Java name, (2) signature,
- * (3) pointer to C function.
- */
-static JNINativeMethod sNativeCryptoMethods[] = {
- { "EVP_PKEY_new_DSA", "([B[B[B[B[B)I", (void*)NativeCrypto_EVP_PKEY_new_DSA },
- { "EVP_PKEY_new_RSA", "([B[B[B[B[B)I", (void*)NativeCrypto_EVP_PKEY_new_RSA },
- { "EVP_PKEY_free", "(I)V", (void*)NativeCrypto_EVP_PKEY_free },
- { "EVP_new", "()I", (void*)NativeCrypto_EVP_new },
- { "EVP_free", "(I)V", (void*)NativeCrypto_EVP_free },
- { "EVP_DigestFinal", "(I[BI)I", (void*)NativeCrypto_EVP_DigestFinal },
- { "EVP_DigestInit", "(ILjava/lang/String;)V", (void*)NativeCrypto_EVP_DigestInit },
- { "EVP_DigestBlockSize", "(I)I", (void*)NativeCrypto_EVP_DigestBlockSize },
- { "EVP_DigestSize", "(I)I", (void*)NativeCrypto_EVP_DigestSize },
- { "EVP_DigestUpdate", "(I[BII)V", (void*)NativeCrypto_EVP_DigestUpdate },
- { "EVP_VerifyInit", "(ILjava/lang/String;)V", (void*)NativeCrypto_EVP_VerifyInit },
- { "EVP_VerifyUpdate", "(I[BII)V", (void*)NativeCrypto_EVP_VerifyUpdate },
- { "EVP_VerifyFinal", "(I[BIII)I", (void*)NativeCrypto_EVP_VerifyFinal }
-};
-
-/**
- * Module scope variables initialized during JNI registration.
- */
-static jfieldID field_Socket_ssl_ctx;
-static jfieldID field_Socket_ssl;
-static jfieldID field_FileDescriptor_descriptor;
-static jfieldID field_Socket_mImpl;
-static jfieldID field_Socket_mFD;
-static jfieldID field_Socket_timeout;
-
-/**
- * Gets the chars of a String object as a '\0'-terminated UTF-8 string,
- * stored in a freshly-allocated BIO memory buffer.
- */
-static BIO *stringToMemBuf(JNIEnv* env, jstring string) {
- jsize byteCount = env->GetStringUTFLength(string);
- LocalArray<1024> buf(byteCount + 1);
- env->GetStringUTFRegion(string, 0, env->GetStringLength(string), &buf[0]);
-
- BIO* result = BIO_new(BIO_s_mem());
- BIO_puts(result, &buf[0]);
- return result;
-}
/**
* Throws an SocketTimeoutException with the given string as a message.
@@ -423,7 +101,7 @@
/**
* Throws an IOException with a message constructed from the current
* SSL errors. This will also log the errors.
- *
+ *
* @param env the JNI environment
* @param sslReturnCode return code from failing SSL function
* @param sslErrorCode error code returned from SSL_get_error()
@@ -435,7 +113,7 @@
char* str;
int ret;
- // First consult the SSL error code for the general message.
+ // First consult the SSL error code for the general message.
switch (sslErrorCode) {
case SSL_ERROR_NONE:
messageStr = "Ok";
@@ -511,7 +189,7 @@
free(allocStr);
allocStr = str;
}
- // For errors during system calls, errno might be our friend.
+ // For errors during system calls, errno might be our friend.
} else if (sslErrorCode == SSL_ERROR_SYSCALL) {
if (asprintf(&str, "%s, %s", allocStr, strerror(errno)) >= 0) {
free(allocStr);
@@ -533,21 +211,20 @@
}
/**
- * Helper function that grabs the ssl pointer out of the given object.
+ * Helper function that grabs the casts an ssl pointer and then checks for nullness.
* If this function returns NULL and <code>throwIfNull</code> is
* passed as <code>true</code>, then this function will call
* <code>throwIOExceptionStr</code> before returning, so in this case of
* NULL, a caller of this function should simply return and allow JNI
* to do its thing.
- *
- * @param env non-null; the JNI environment
- * @param obj non-null; socket object
+ *
+ * @param env the JNI environment
+ * @param ssl_address; the ssl_address pointer as an integer
* @param throwIfNull whether to throw if the SSL pointer is NULL
* @returns the pointer, which may be NULL
*/
-static SSL *getSslPointer(JNIEnv* env, jobject obj, bool throwIfNull) {
- SSL *ssl = (SSL *)env->GetIntField(obj, field_Socket_ssl);
-
+static SSL* getSslPointer(JNIEnv* env, int ssl_address, bool throwIfNull) {
+ SSL* ssl = reinterpret_cast<SSL*>(static_cast<uintptr_t>(ssl_address));
if ((ssl == NULL) && throwIfNull) {
throwIOExceptionStr(env, "null SSL pointer");
}
@@ -555,9 +232,19 @@
return ssl;
}
-// ============================================================================
-// === OpenSSL-related helper stuff begins here. ==============================
-// ============================================================================
+/**
+ * Converts a Java byte[] to an OpenSSL BIGNUM, allocating the BIGNUM on the
+ * fly.
+ */
+static BIGNUM* arrayToBignum(JNIEnv* env, jbyteArray source) {
+ // LOGD("Entering arrayToBignum()");
+
+ jbyte* sourceBytes = env->GetByteArrayElements(source, NULL);
+ int sourceLength = env->GetArrayLength(source);
+ BIGNUM* bignum = BN_bin2bn((unsigned char*) sourceBytes, sourceLength, NULL);
+ env->ReleaseByteArrayElements(source, sourceBytes, JNI_ABORT);
+ return bignum;
+}
/**
* OpenSSL locking support. Taken from the O'Reilly book by Viega et al., but I
@@ -626,59 +313,611 @@
return 1;
}
-int get_socket_timeout(int type, int sd) {
- struct timeval tv;
- socklen_t len = sizeof(tv);
- if (getsockopt(sd, SOL_SOCKET, type, &tv, &len) < 0) {
- LOGE("getsockopt(%d, SOL_SOCKET): %s (%d)",
- sd,
- strerror(errno),
- errno);
- return 0;
+/**
+ * Initialization phase for every OpenSSL job: Loads the Error strings, the
+ * crypto algorithms and reset the OpenSSL library
+ */
+static void NativeCrypto_clinit(JNIEnv* env, jclass)
+{
+ SSL_load_error_strings();
+ ERR_load_crypto_strings();
+ SSL_library_init();
+ OpenSSL_add_all_algorithms();
+ THREAD_setup();
+}
+
+/**
+ * public static native int EVP_PKEY_new_DSA(byte[] p, byte[] q, byte[] g, byte[] pub_key, byte[] priv_key);
+ */
+static EVP_PKEY* NativeCrypto_EVP_PKEY_new_DSA(JNIEnv* env, jclass clazz, jbyteArray p, jbyteArray q, jbyteArray g, jbyteArray pub_key, jbyteArray priv_key) {
+ // LOGD("Entering EVP_PKEY_new_DSA()");
+
+ DSA* dsa = DSA_new();
+
+ dsa->p = arrayToBignum(env, p);
+ dsa->q = arrayToBignum(env, q);
+ dsa->g = arrayToBignum(env, g);
+ dsa->pub_key = arrayToBignum(env, pub_key);
+
+ if (priv_key != NULL) {
+ dsa->priv_key = arrayToBignum(env, priv_key);
}
- // LOGI("Current socket timeout (%d(s), %d(us))!",
- // (int)tv.tv_sec, (int)tv.tv_usec);
- int timeout = tv.tv_sec * 1000 + tv.tv_usec / 1000;
- return timeout;
-}
-#ifdef TIMEOUT_DEBUG_SSL
-
-void print_socket_timeout(const char* name, int type, int sd) {
- struct timeval tv;
- int len = sizeof(tv);
- if (getsockopt(sd, SOL_SOCKET, type, &tv, &len) < 0) {
- LOGE("getsockopt(%d, SOL_SOCKET, %s): %s (%d)",
- sd,
- name,
- strerror(errno),
- errno);
+ if (dsa->p == NULL || dsa->q == NULL || dsa->g == NULL || dsa->pub_key == NULL) {
+ DSA_free(dsa);
+ jniThrowRuntimeException(env, "Unable to convert BigInteger to BIGNUM");
+ return NULL;
}
- LOGI("Current socket %s is (%d(s), %d(us))!",
- name, (int)tv.tv_sec, (int)tv.tv_usec);
+
+ EVP_PKEY* pkey = EVP_PKEY_new();
+ EVP_PKEY_assign_DSA(pkey, dsa);
+
+ return pkey;
}
-void print_timeout(const char* method, SSL* ssl) {
- LOGI("SSL_get_default_timeout %d in %s", SSL_get_default_timeout(ssl), method);
- int fd = SSL_get_fd(ssl);
- print_socket_timeout("SO_RCVTIMEO", SO_RCVTIMEO, fd);
- print_socket_timeout("SO_SNDTIMEO", SO_SNDTIMEO, fd);
+/**
+ * private static native int EVP_PKEY_new_RSA(byte[] n, byte[] e, byte[] d, byte[] p, byte[] q);
+ */
+static EVP_PKEY* NativeCrypto_EVP_PKEY_new_RSA(JNIEnv* env, jclass clazz, jbyteArray n, jbyteArray e, jbyteArray d, jbyteArray p, jbyteArray q) {
+ // LOGD("Entering EVP_PKEY_new_RSA()");
+
+ RSA* rsa = RSA_new();
+
+ rsa->n = arrayToBignum(env, n);
+ rsa->e = arrayToBignum(env, e);
+
+ if (d != NULL) {
+ rsa->d = arrayToBignum(env, d);
+ }
+
+ if (p != NULL) {
+ rsa->p = arrayToBignum(env, p);
+ }
+
+ if (q != NULL) {
+ rsa->q = arrayToBignum(env, q);
+ }
+
+ // int check = RSA_check_key(rsa);
+ // LOGI("RSA_check_key returns %d", check);
+
+ if (rsa->n == NULL || rsa->e == NULL) {
+ RSA_free(rsa);
+ jniThrowRuntimeException(env, "Unable to convert BigInteger to BIGNUM");
+ return NULL;
+ }
+
+ EVP_PKEY* pkey = EVP_PKEY_new();
+ EVP_PKEY_assign_RSA(pkey, rsa);
+
+ return pkey;
}
+/**
+ * private static native void EVP_PKEY_free(int pkey);
+ */
+static void NativeCrypto_EVP_PKEY_free(JNIEnv* env, jclass clazz, EVP_PKEY* pkey) {
+ // LOGD("Entering EVP_PKEY_free()");
+
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+}
+
+/*
+ * public static native int EVP_new()
+ */
+static jint NativeCrypto_EVP_new(JNIEnv* env, jclass clazz) {
+ // LOGI("NativeCrypto_EVP_DigestNew");
+
+ return (jint)EVP_MD_CTX_create();
+}
+
+/*
+ * public static native void EVP_free(int)
+ */
+static void NativeCrypto_EVP_free(JNIEnv* env, jclass clazz, EVP_MD_CTX* ctx) {
+ // LOGI("NativeCrypto_EVP_DigestFree");
+
+ if (ctx != NULL) {
+ EVP_MD_CTX_destroy(ctx);
+ }
+}
+
+/*
+ * public static native int EVP_DigestFinal(int, byte[], int)
+ */
+static jint NativeCrypto_EVP_DigestFinal(JNIEnv* env, jclass clazz, EVP_MD_CTX* ctx, jbyteArray hash, jint offset) {
+ // LOGI("NativeCrypto_EVP_DigestFinal%x, %x, %d, %d", ctx, hash, offset);
+
+ if (ctx == NULL || hash == NULL) {
+ jniThrowNullPointerException(env, NULL);
+ return -1;
+ }
+
+ int result = -1;
+
+ jbyte* hashBytes = env->GetByteArrayElements(hash, NULL);
+ EVP_DigestFinal(ctx, (unsigned char*) (hashBytes + offset), (unsigned int*)&result);
+ env->ReleaseByteArrayElements(hash, hashBytes, 0);
+
+ throwExceptionIfNecessary(env);
+
+ return result;
+}
+
+/*
+ * public static native void EVP_DigestInit(int, java.lang.String)
+ */
+static void NativeCrypto_EVP_DigestInit(JNIEnv* env, jclass clazz, EVP_MD_CTX* ctx, jstring algorithm) {
+ // LOGI("NativeCrypto_EVP_DigestInit");
+
+ if (ctx == NULL || algorithm == NULL) {
+ jniThrowNullPointerException(env, NULL);
+ return;
+ }
+
+ const char* algorithmChars = env->GetStringUTFChars(algorithm, NULL);
+
+ const EVP_MD *digest = EVP_get_digestbynid(OBJ_txt2nid(algorithmChars));
+ env->ReleaseStringUTFChars(algorithm, algorithmChars);
+
+ if (digest == NULL) {
+ jniThrowRuntimeException(env, "Hash algorithm not found");
+ return;
+ }
+
+ EVP_DigestInit(ctx, digest);
+
+ throwExceptionIfNecessary(env);
+}
+
+/*
+ * public static native void EVP_DigestSize(int)
+ */
+static jint NativeCrypto_EVP_DigestSize(JNIEnv* env, jclass clazz, EVP_MD_CTX* ctx) {
+ // LOGI("NativeCrypto_EVP_DigestSize");
+
+ if (ctx == NULL) {
+ jniThrowNullPointerException(env, NULL);
+ return -1;
+ }
+
+ int result = EVP_MD_CTX_size(ctx);
+
+ throwExceptionIfNecessary(env);
+
+ return result;
+}
+
+/*
+ * public static native void EVP_DigestBlockSize(int)
+ */
+static jint NativeCrypto_EVP_DigestBlockSize(JNIEnv* env, jclass clazz, EVP_MD_CTX* ctx) {
+ // LOGI("NativeCrypto_EVP_DigestBlockSize");
+
+ if (ctx == NULL) {
+ jniThrowNullPointerException(env, NULL);
+ return -1;
+ }
+
+ int result = EVP_MD_CTX_block_size(ctx);
+
+ throwExceptionIfNecessary(env);
+
+ return result;
+}
+
+/*
+ * public static native void EVP_DigestUpdate(int, byte[], int, int)
+ */
+static void NativeCrypto_EVP_DigestUpdate(JNIEnv* env, jclass clazz, EVP_MD_CTX* ctx, jbyteArray buffer, jint offset, jint length) {
+ // LOGI("NativeCrypto_EVP_DigestUpdate %x, %x, %d, %d", ctx, buffer, offset, length);
+
+ if (ctx == NULL || buffer == NULL) {
+ jniThrowNullPointerException(env, NULL);
+ return;
+ }
+
+ jbyte* bufferBytes = env->GetByteArrayElements(buffer, NULL);
+ EVP_DigestUpdate(ctx, (unsigned char*) (bufferBytes + offset), length);
+ env->ReleaseByteArrayElements(buffer, bufferBytes, JNI_ABORT);
+
+ throwExceptionIfNecessary(env);
+}
+
+/*
+ * public static native void EVP_VerifyInit(int, java.lang.String)
+ */
+static void NativeCrypto_EVP_VerifyInit(JNIEnv* env, jclass clazz, EVP_MD_CTX* ctx, jstring algorithm) {
+ // LOGI("NativeCrypto_EVP_VerifyInit");
+
+ if (ctx == NULL || algorithm == NULL) {
+ jniThrowNullPointerException(env, NULL);
+ return;
+ }
+
+ const char* algorithmChars = env->GetStringUTFChars(algorithm, NULL);
+
+ const EVP_MD *digest = EVP_get_digestbynid(OBJ_txt2nid(algorithmChars));
+ env->ReleaseStringUTFChars(algorithm, algorithmChars);
+
+ if (digest == NULL) {
+ jniThrowRuntimeException(env, "Hash algorithm not found");
+ return;
+ }
+
+ EVP_VerifyInit(ctx, digest);
+
+ throwExceptionIfNecessary(env);
+}
+
+/*
+ * public static native void EVP_VerifyUpdate(int, byte[], int, int)
+ */
+static void NativeCrypto_EVP_VerifyUpdate(JNIEnv* env, jclass clazz, EVP_MD_CTX* ctx, jbyteArray buffer, jint offset, jint length) {
+ // LOGI("NativeCrypto_EVP_VerifyUpdate %x, %x, %d, %d", ctx, buffer, offset, length);
+
+ if (ctx == NULL || buffer == NULL) {
+ jniThrowNullPointerException(env, NULL);
+ return;
+ }
+
+ jbyte* bufferBytes = env->GetByteArrayElements(buffer, NULL);
+ EVP_VerifyUpdate(ctx, (unsigned char*) (bufferBytes + offset), length);
+ env->ReleaseByteArrayElements(buffer, bufferBytes, JNI_ABORT);
+
+ throwExceptionIfNecessary(env);
+}
+
+/*
+ * public static native void EVP_VerifyFinal(int, byte[], int, int, int)
+ */
+static int NativeCrypto_EVP_VerifyFinal(JNIEnv* env, jclass clazz, EVP_MD_CTX* ctx, jbyteArray buffer, jint offset, jint length, EVP_PKEY* pkey) {
+ // LOGI("NativeCrypto_EVP_VerifyFinal %x, %x, %d, %d %x", ctx, buffer, offset, length, pkey);
+
+ if (ctx == NULL || buffer == NULL || pkey == NULL) {
+ jniThrowNullPointerException(env, NULL);
+ return -1;
+ }
+
+ jbyte* bufferBytes = env->GetByteArrayElements(buffer, NULL);
+ int result = EVP_VerifyFinal(ctx, (unsigned char*) (bufferBytes + offset), length, pkey);
+ env->ReleaseByteArrayElements(buffer, bufferBytes, JNI_ABORT);
+
+ throwExceptionIfNecessary(env);
+
+ return result;
+}
+
+/**
+ * Convert ssl version constant to string. Based on SSL_get_version
+ */
+static const char* get_ssl_version(int ssl_version) {
+ switch (ssl_version) {
+ // newest to oldest
+ case TLS1_VERSION: {
+ return SSL_TXT_TLSV1;
+ }
+ case SSL3_VERSION: {
+ return SSL_TXT_SSLV3;
+ }
+ case SSL2_VERSION: {
+ return SSL_TXT_SSLV2;
+ }
+ default: {
+ return "unknown";
+ }
+ }
+}
+
+/**
+ * Convert content type constant to string.
+ */
+static const char* get_content_type(int content_type) {
+ switch (content_type) {
+ case SSL3_RT_CHANGE_CIPHER_SPEC: {
+ return "SSL3_RT_CHANGE_CIPHER_SPEC";
+ }
+ case SSL3_RT_ALERT: {
+ return "SSL3_RT_ALERT";
+ }
+ case SSL3_RT_HANDSHAKE: {
+ return "SSL3_RT_HANDSHAKE";
+ }
+ case SSL3_RT_APPLICATION_DATA: {
+ return "SSL3_RT_APPLICATION_DATA";
+ }
+ default: {
+ LOGD("Unknown TLS/SSL content type %d", content_type);
+ return "<unknown>";
+ }
+ }
+}
+
+/**
+ * Simple logging call back to show hand shake messages
+ */
+static void ssl_msg_callback_LOG(int write_p, int ssl_version, int content_type,
+ const void *buf, size_t len, SSL* ssl, void* arg) {
+ LOGD("SSL %p %s %s %s %p %d %p",
+ ssl,
+ (write_p) ? "send" : "recv",
+ get_ssl_version(ssl_version),
+ get_content_type(content_type),
+ buf,
+ len,
+ arg);
+}
+
+/*
+ * public static native int SSL_CTX_new();
+ */
+static int NativeCrypto_SSL_CTX_new(JNIEnv* env, jclass clazz) {
+ SSL_CTX* sslCtx = SSL_CTX_new(SSLv23_method());
+ // Note: We explicitly do not allow SSLv2 to be used.
+ SSL_CTX_set_options(sslCtx, SSL_OP_ALL | SSL_OP_NO_SSLv2);
+
+ int mode = SSL_CTX_get_mode(sslCtx);
+ /*
+ * Turn on "partial write" mode. This means that SSL_write() will
+ * behave like Posix write() and possibly return after only
+ * writing a partial buffer. Note: The alternative, perhaps
+ * surprisingly, is not that SSL_write() always does full writes
+ * but that it will force you to retry write calls having
+ * preserved the full state of the original call. (This is icky
+ * and undesirable.)
+ */
+ mode |= SSL_MODE_ENABLE_PARTIAL_WRITE;
+#if defined(SSL_MODE_SMALL_BUFFERS) /* not all SSL versions have this */
+ mode |= SSL_MODE_SMALL_BUFFERS; /* lazily allocate record buffers; usually saves
+ * 44k over the default */
#endif
+#if defined(SSL_MODE_HANDSHAKE_CUTTHROUGH) /* not all SSL versions have this */
+ mode |= SSL_MODE_HANDSHAKE_CUTTHROUGH; /* enable sending of client data as soon as
+ * ClientCCS and ClientFinished are sent */
+#endif
+ SSL_CTX_set_mode(sslCtx, mode);
+
+ // SSL_CTX_set_msg_callback(sslCtx, ssl_msg_callback_LOG); /* enable for handshake debug */
+ return (jint) sslCtx;
+}
+
+static jobjectArray makeCipherList(JNIEnv* env, STACK_OF(SSL_CIPHER)* cipher_list) {
+ // Create a String[].
+ jclass stringClass = env->FindClass("java/lang/String");
+ if (stringClass == NULL) {
+ return NULL;
+ }
+ int cipherCount = sk_SSL_CIPHER_num(cipher_list);
+ jobjectArray array = env->NewObjectArray(cipherCount, stringClass, NULL);
+ if (array == NULL) {
+ return NULL;
+ }
+
+ // Fill in the cipher names.
+ for (int i = 0; i < cipherCount; ++i) {
+ const char* c = sk_SSL_CIPHER_value(cipher_list, i)->name;
+ env->SetObjectArrayElement(array, i, env->NewStringUTF(c));
+ }
+ return array;
+}
+
+/**
+ * Loads the ciphers suites that are supported by an SSL_CTX
+ * and returns them in a string array.
+ */
+static jobjectArray NativeCrypto_SSL_CTX_get_ciphers(JNIEnv* env,
+ jclass, jint ssl_ctx_address)
+{
+ SSL_CTX* ssl_ctx = reinterpret_cast<SSL_CTX*>(static_cast<uintptr_t>(ssl_ctx_address));
+ if (ssl_ctx == NULL) {
+ jniThrowNullPointerException(env, "SSL_CTX is null");
+ return NULL;
+ }
+ return makeCipherList(env, ssl_ctx->cipher_list);
+}
+
+/**
+ * public static native void SSL_CTX_free(int ssl_ctx)
+ */
+static void NativeCrypto_SSL_CTX_free(JNIEnv* env,
+ jclass, jint ssl_ctx_address)
+{
+ SSL_CTX* ssl_ctx = reinterpret_cast<SSL_CTX*>(static_cast<uintptr_t>(ssl_ctx_address));
+ if (ssl_ctx == NULL) {
+ jniThrowNullPointerException(env, "SSL_CTX is null");
+ return;
+ }
+ SSL_CTX_free(ssl_ctx);
+}
+
+/**
+ * Gets the chars of a String object as a '\0'-terminated UTF-8 string,
+ * stored in a freshly-allocated BIO memory buffer.
+ */
+static BIO *stringToMemBuf(JNIEnv* env, jstring string) {
+ jsize byteCount = env->GetStringUTFLength(string);
+ LocalArray<1024> buf(byteCount + 1);
+ env->GetStringUTFRegion(string, 0, env->GetStringLength(string), &buf[0]);
+
+ BIO* result = BIO_new(BIO_s_mem());
+ BIO_puts(result, &buf[0]);
+ return result;
+}
+
+/**
+ * public static native int SSL_new(int ssl_ctx, String privatekey, String certificate, byte[] seed) throws IOException;
+ */
+static jint NativeCrypto_SSL_new(JNIEnv* env, jclass,
+ jint ssl_ctx_address, jstring privatekey, jstring certificates, jbyteArray seed)
+{
+ SSL_CTX* ssl_ctx = reinterpret_cast<SSL_CTX*>(static_cast<uintptr_t>(ssl_ctx_address));
+ if (ssl_ctx == NULL) {
+ jniThrowNullPointerException(env, "SSL_CTX is null");
+ return 0;
+ }
+
+ // 'seed == null' when no SecureRandom Object is set
+ // in the SSLContext.
+ if (seed != NULL) {
+ jbyte* randseed = env->GetByteArrayElements(seed, NULL);
+ RAND_seed((unsigned char*) randseed, 1024);
+ env->ReleaseByteArrayElements(seed, randseed, 0);
+ } else {
+ RAND_load_file("/dev/urandom", 1024);
+ }
+
+ SSL* ssl = SSL_new(ssl_ctx);
+ if (ssl == NULL) {
+ throwIOExceptionWithSslErrors(env, 0, 0,
+ "Unable to create SSL structure");
+ return NULL;
+ }
+
+ /* Java code in class OpenSSLSocketImpl does the verification. Meaning of
+ * SSL_VERIFY_NONE flag in client mode: if not using an anonymous cipher
+ * (by default disabled), the server will send a certificate which will
+ * be checked. The result of the certificate verification process can be
+ * checked after the TLS/SSL handshake using the SSL_get_verify_result(3)
+ * function. The handshake will be continued regardless of the
+ * verification result.
+ */
+ SSL_set_verify(ssl, SSL_VERIFY_NONE, NULL);
+
+ if (privatekey != NULL) {
+ BIO* privatekeybio = stringToMemBuf(env, (jstring) privatekey);
+ EVP_PKEY* privatekeyevp =
+ PEM_read_bio_PrivateKey(privatekeybio, NULL, 0, NULL);
+ BIO_free(privatekeybio);
+
+ if (privatekeyevp == NULL) {
+ LOGE(ERR_error_string(ERR_get_error(), NULL));
+ throwIOExceptionWithSslErrors(env, 0, 0,
+ "Error parsing the private key");
+ SSL_free(ssl);
+ return NULL;
+ }
+
+ BIO* certificatesbio = stringToMemBuf(env, (jstring) certificates);
+ X509* certificatesx509 =
+ PEM_read_bio_X509(certificatesbio, NULL, 0, NULL);
+ BIO_free(certificatesbio);
+
+ if (certificatesx509 == NULL) {
+ LOGE(ERR_error_string(ERR_get_error(), NULL));
+ throwIOExceptionWithSslErrors(env, 0, 0,
+ "Error parsing the certificates");
+ EVP_PKEY_free(privatekeyevp);
+ SSL_free(ssl);
+ return NULL;
+ }
+
+ int ret = SSL_use_certificate(ssl, certificatesx509);
+ if (ret != 1) {
+ LOGE(ERR_error_string(ERR_get_error(), NULL));
+ throwIOExceptionWithSslErrors(env, ret, 0,
+ "Error setting the certificates");
+ X509_free(certificatesx509);
+ EVP_PKEY_free(privatekeyevp);
+ SSL_free(ssl);
+ return NULL;
+ }
+
+ ret = SSL_use_PrivateKey(ssl, privatekeyevp);
+ if (ret != 1) {
+ LOGE(ERR_error_string(ERR_get_error(), NULL));
+ throwIOExceptionWithSslErrors(env, ret, 0,
+ "Error setting the private key");
+ X509_free(certificatesx509);
+ EVP_PKEY_free(privatekeyevp);
+ SSL_free(ssl);
+ return NULL;
+ }
+
+ ret = SSL_check_private_key(ssl);
+ if (ret != 1) {
+ throwIOExceptionWithSslErrors(env, ret, 0,
+ "Error checking the private key");
+ X509_free(certificatesx509);
+ EVP_PKEY_free(privatekeyevp);
+ SSL_free(ssl);
+ return NULL;
+ }
+ }
+ return (jint)ssl;
+}
+
+/**
+ * public static native long SSL_get_options(int ssl);
+ */
+static jlong NativeCrypto_SSL_get_options(JNIEnv* env, jclass,
+ jint ssl_address) {
+ SSL* ssl = getSslPointer(env, ssl_address, true);
+ if (ssl == NULL) {
+ return 0;
+ }
+ return SSL_get_options(ssl);
+}
+
+/**
+ * public static native long SSL_set_options(int ssl, long options);
+ */
+static jlong NativeCrypto_SSL_set_options(JNIEnv* env, jclass,
+ jint ssl_address, jlong options) {
+ SSL* ssl = getSslPointer(env, ssl_address, true);
+ if (ssl == NULL) {
+ return 0 ;
+ }
+ return SSL_set_options(ssl, options);
+}
+
+/**
+ * Loads the ciphers suites that are enabled in the SSL
+ * and returns them in a string array.
+ */
+static jobjectArray NativeCrypto_SSL_get_ciphers(JNIEnv* env,
+ jclass, jint ssl_address)
+{
+ SSL* ssl = getSslPointer(env, ssl_address, true);
+ if (ssl == NULL) {
+ return NULL;
+ }
+ return makeCipherList(env, SSL_get_ciphers(ssl));
+}
+
+/**
+ * Sets the ciphers suites that are enabled in the SSL
+ */
+static void NativeCrypto_SSL_set_cipher_list(JNIEnv* env, jclass,
+ jint ssl_address, jstring controlString)
+{
+ SSL* ssl = getSslPointer(env, ssl_address, true);
+ if (ssl == NULL) {
+ return;
+ }
+ const char* str = env->GetStringUTFChars(controlString, NULL);
+ int rc = SSL_set_cipher_list(ssl, str);
+ env->ReleaseStringUTFChars(controlString, str);
+ if (rc == 0) {
+ freeSslErrorState();
+ jniThrowException(env, "java/lang/IllegalArgumentException",
+ "Illegal cipher suite strings.");
+ }
+}
/**
* Our additional application data needed for getting synchronization right.
* This maybe warrants a bit of lengthy prose:
- *
+ *
* (1) We use a flag to reflect whether we consider the SSL connection alive.
* Any read or write attempt loops will be cancelled once this flag becomes 0.
- *
+ *
* (2) We use an int to count the number of threads that are blocked by the
* underlying socket. This may be at most two (one reader and one writer), since
* the Java layer ensures that no more threads will enter the native code at the
* same time.
- *
+ *
* (3) The pipe is used primarily as a means of cancelling a blocking select()
* when we want to close the connection (aka "emergency button"). It is also
* necessary for dealing with a possible race condition situation: There might
@@ -687,23 +926,23 @@
* If one leaves the select() successfully before the other enters it, the
* "success" event is already consumed and the second thread will be blocked,
* possibly forever (depending on network conditions).
- *
+ *
* The idea for solving the problem looks like this: Whenever a thread is
* successful in moving around data on the network, and it knows there is
* another thread stuck in a select(), it will write a byte to the pipe, waking
* up the other thread. A thread that returned from select(), on the other hand,
* knows whether it's been woken up by the pipe. If so, it will consume the
* byte, and the original state of affairs has been restored.
- *
+ *
* The pipe may seem like a bit of overhead, but it fits in nicely with the
* other file descriptors of the select(), so there's only one condition to wait
* for.
- *
+ *
* (4) Finally, a mutex is needed to make sure that at most one thread is in
* either SSL_read() or SSL_write() at any given time. This is an OpenSSL
* requirement. We use the same mutex to guard the field for counting the
* waiting threads.
- *
+ *
* Note: The current implementation assumes that we don't have to deal with
* problems induced by multiple cores or processors and their respective
* memory caches. One possible problem is that of inconsistent views on the
@@ -712,7 +951,7 @@
* currently this seems a bit like overkill.
*/
typedef struct app_data {
- int aliveAndKicking;
+ volatile int aliveAndKicking;
int waitingThreads;
int fdsEmergency[2];
MUTEX_TYPE mutex;
@@ -720,7 +959,7 @@
/**
* Creates our application data and attaches it to a given SSL connection.
- *
+ *
* @param ssl The SSL connection to attach the data to.
* @return 0 on success, -1 on failure.
*/
@@ -751,16 +990,16 @@
/**
* Destroys our application data, cleaning up everything in the process.
- *
+ *
* @param ssl The SSL connection to take the data from.
- */
+ */
static void sslDestroyAppData(SSL* ssl) {
APP_DATA* data = (APP_DATA*) SSL_get_app_data(ssl);
if (data != NULL) {
SSL_set_app_data(ssl, NULL);
- data -> aliveAndKicking = 0;
+ data->aliveAndKicking = 0;
if (data->fdsEmergency[0] != -1) {
close(data->fdsEmergency[0]);
@@ -776,72 +1015,101 @@
}
}
-
/**
- * Frees the SSL_CTX struct for the given instance.
+ * public static native void SSL_free(int ssl);
*/
-static void free_ssl_ctx(JNIEnv* env, jobject object) {
- /*
- * Preserve and restore the exception state around this call, so
- * that GetIntField and SetIntField will operate without complaint.
- */
- jthrowable exception = env->ExceptionOccurred();
-
- if (exception != NULL) {
- env->ExceptionClear();
+static void NativeCrypto_SSL_free(JNIEnv* env, jclass, jint ssl_address)
+{
+ SSL* ssl = getSslPointer(env, ssl_address, true);
+ if (ssl == NULL) {
+ return;
}
-
- SSL_CTX *ctx = (SSL_CTX *)env->GetIntField(object, field_Socket_ssl_ctx);
-
- if (ctx != NULL) {
- SSL_CTX_free(ctx);
- env->SetIntField(object, field_Socket_ssl_ctx, (int) NULL);
- }
-
- if (exception != NULL) {
- env->Throw(exception);
- }
+ sslDestroyAppData(ssl);
+ SSL_free(ssl);
}
-/**
- * Frees the SSL struct for the given instance.
+/*
+ * Defines the mapping from Java methods and their signatures
+ * to native functions. Order is (1) Java name, (2) signature,
+ * (3) pointer to C function.
*/
-static void free_ssl(JNIEnv* env, jobject object) {
- /*
- * Preserve and restore the exception state around this call, so
- * that GetIntField and SetIntField will operate without complaint.
- */
- jthrowable exception = env->ExceptionOccurred();
-
- if (exception != NULL) {
- env->ExceptionClear();
- }
-
- SSL *ssl = (SSL *)env->GetIntField(object, field_Socket_ssl);
-
- if (ssl != NULL) {
- sslDestroyAppData(ssl);
- SSL_free(ssl);
- env->SetIntField(object, field_Socket_ssl, (int) NULL);
- }
-
- if (exception != NULL) {
- env->Throw(exception);
- }
-}
+static JNINativeMethod sNativeCryptoMethods[] = {
+ { "clinit", "()V", (void*)NativeCrypto_clinit},
+ { "EVP_PKEY_new_DSA", "([B[B[B[B[B)I", (void*)NativeCrypto_EVP_PKEY_new_DSA },
+ { "EVP_PKEY_new_RSA", "([B[B[B[B[B)I", (void*)NativeCrypto_EVP_PKEY_new_RSA },
+ { "EVP_PKEY_free", "(I)V", (void*)NativeCrypto_EVP_PKEY_free },
+ { "EVP_new", "()I", (void*)NativeCrypto_EVP_new },
+ { "EVP_free", "(I)V", (void*)NativeCrypto_EVP_free },
+ { "EVP_DigestFinal", "(I[BI)I", (void*)NativeCrypto_EVP_DigestFinal },
+ { "EVP_DigestInit", "(ILjava/lang/String;)V", (void*)NativeCrypto_EVP_DigestInit },
+ { "EVP_DigestBlockSize", "(I)I", (void*)NativeCrypto_EVP_DigestBlockSize },
+ { "EVP_DigestSize", "(I)I", (void*)NativeCrypto_EVP_DigestSize },
+ { "EVP_DigestUpdate", "(I[BII)V", (void*)NativeCrypto_EVP_DigestUpdate },
+ { "EVP_VerifyInit", "(ILjava/lang/String;)V", (void*)NativeCrypto_EVP_VerifyInit },
+ { "EVP_VerifyUpdate", "(I[BII)V", (void*)NativeCrypto_EVP_VerifyUpdate },
+ { "EVP_VerifyFinal", "(I[BIII)I", (void*)NativeCrypto_EVP_VerifyFinal },
+ { "SSL_CTX_new", "()I", (void*)NativeCrypto_SSL_CTX_new },
+ { "SSL_CTX_get_ciphers", "(I)[Ljava/lang/String;", (void*)NativeCrypto_SSL_CTX_get_ciphers},
+ { "SSL_CTX_free", "(I)V", (void*)NativeCrypto_SSL_CTX_free },
+ { "SSL_new", "(ILjava/lang/String;Ljava/lang/String;[B)I", (void*)NativeCrypto_SSL_new},
+ { "SSL_get_options", "(I)J", (void*)NativeCrypto_SSL_get_options },
+ { "SSL_set_options", "(IJ)J", (void*)NativeCrypto_SSL_set_options },
+ { "SSL_get_ciphers", "(I)[Ljava/lang/String;", (void*)NativeCrypto_SSL_get_ciphers},
+ { "SSL_set_cipher_list", "(ILjava/lang/String;)V", (void*)NativeCrypto_SSL_set_cipher_list},
+ { "SSL_free", "(I)V", (void*)NativeCrypto_SSL_free},
+};
/**
- * Constructs the SSL struct for the given instance, replacing one
- * that was already made, if any.
+ * Module scope variables initialized during JNI registration.
*/
-static SSL* create_ssl(JNIEnv* env, jobject object, SSL_CTX* ssl_ctx) {
- free_ssl(env, object);
+static jfieldID field_Socket_mImpl;
+static jfieldID field_Socket_mFD;
- SSL *ssl = SSL_new(ssl_ctx);
- env->SetIntField(object, field_Socket_ssl, (int) ssl);
- return ssl;
+// ============================================================================
+// === OpenSSL-related helper stuff begins here. ==============================
+// ============================================================================
+
+int get_socket_timeout(int type, int sd) {
+ struct timeval tv;
+ socklen_t len = sizeof(tv);
+ if (getsockopt(sd, SOL_SOCKET, type, &tv, &len) < 0) {
+ LOGE("getsockopt(%d, SOL_SOCKET): %s (%d)",
+ sd,
+ strerror(errno),
+ errno);
+ return 0;
+ }
+ // LOGI("Current socket timeout (%d(s), %d(us))!",
+ // (int)tv.tv_sec, (int)tv.tv_usec);
+ int timeout = tv.tv_sec * 1000 + tv.tv_usec / 1000;
+ return timeout;
}
+#ifdef TIMEOUT_DEBUG_SSL
+
+void print_socket_timeout(const char* name, int type, int sd) {
+ struct timeval tv;
+ int len = sizeof(tv);
+ if (getsockopt(sd, SOL_SOCKET, type, &tv, &len) < 0) {
+ LOGE("getsockopt(%d, SOL_SOCKET, %s): %s (%d)",
+ sd,
+ name,
+ strerror(errno),
+ errno);
+ }
+ LOGI("Current socket %s is (%d(s), %d(us))!",
+ name, (int)tv.tv_sec, (int)tv.tv_usec);
+}
+
+void print_timeout(const char* method, SSL* ssl) {
+ LOGI("SSL_get_default_timeout %d in %s", SSL_get_default_timeout(ssl), method);
+ int fd = SSL_get_fd(ssl);
+ print_socket_timeout("SO_RCVTIMEO", SO_RCVTIMEO, fd);
+ print_socket_timeout("SO_SNDTIMEO", SO_SNDTIMEO, fd);
+}
+
+#endif
+
/**
* Dark magic helper function that checks, for a given SSL session, whether it
* can SSL_read() or SSL_write() without blocking. Takes into account any
@@ -851,15 +1119,15 @@
* specifies whether we are waiting for readability or writability. It expects
* to be passed either SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE, since we
* only need to wait in case one of these problems occurs.
- *
+ *
* @param type Either SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE
* @param fd The file descriptor to wait for (the underlying socket)
- * @param data The application data structure with mutex info etc.
+ * @param data The application data structure with mutex info etc.
* @param timeout The timeout value for select call, with the special value
* 0 meaning no timeout at all (wait indefinitely). Note: This is
* the Java semantics of the timeout value, not the usual
* select() semantics.
- * @return The result of the inner select() call, -1 on additional errors
+ * @return The result of the inner select() call, -1 on additional errors
*/
static int sslSelect(int type, int fd, APP_DATA *data, int timeout) {
fd_set rfds;
@@ -888,16 +1156,16 @@
} else {
ptv = NULL;
}
-
+
// LOGD("Doing select() for SSL_ERROR_WANT_%s...", type == SSL_ERROR_WANT_READ ? "READ" : "WRITE");
int result = select(max + 1, &rfds, &wfds, NULL, ptv);
// LOGD("Returned from select(), result is %d", result);
-
+
// Lock
if (MUTEX_LOCK(data->mutex) == -1) {
return -1;
}
-
+
// If we have been woken up by the emergency pipe, there must be a token in
// it. Thus we can safely read it (even in a blocking way).
if (FD_ISSET(data->fdsEmergency[0], &rfds)) {
@@ -910,7 +1178,7 @@
// Tell the world that there is now one thread less waiting for the
// underlying network.
data->waitingThreads--;
-
+
// Unlock
MUTEX_UNLOCK(data->mutex);
// LOGD("leave sslSelect");
@@ -921,8 +1189,8 @@
* Helper function that wakes up a thread blocked in select(), in case there is
* one. Is being called by sslRead() and sslWrite() as well as by JNI glue
* before closing the connection.
- *
- * @param data The application data structure with mutex info etc.
+ *
+ * @param data The application data structure with mutex info etc.
*/
static void sslNotify(APP_DATA *data) {
// Write a byte to the emergency pipe, so a concurrent select() can return.
@@ -940,7 +1208,7 @@
/**
* Helper function which does the actual reading. The Java layer guarantees that
* at most one thread will enter this function at any given time.
- *
+ *
* @param ssl non-null; the SSL context
* @param buf non-null; buffer to read into
* @param len length of the buffer, in bytes
@@ -953,7 +1221,7 @@
int* sslErrorCode, int timeout) {
// LOGD("Entering sslRead, caller requests to read %d bytes...", len);
-
+
if (len == 0) {
// Don't bother doing anything in this case.
return 0;
@@ -961,7 +1229,7 @@
int fd = SSL_get_fd(ssl);
BIO *bio = SSL_get_rbio(ssl);
-
+
APP_DATA* data = (APP_DATA*) SSL_get_app_data(ssl);
while (data->aliveAndKicking) {
@@ -973,7 +1241,7 @@
}
unsigned int bytesMoved = BIO_number_read(bio) + BIO_number_written(bio);
-
+
// LOGD("Doing SSL_Read()");
int result = SSL_read(ssl, buf, len);
int error = SSL_ERROR_NONE;
@@ -989,13 +1257,13 @@
if (BIO_number_read(bio) + BIO_number_written(bio) != bytesMoved && data->waitingThreads > 0) {
sslNotify(data);
}
-
+
// If we are blocked by the underlying socket, tell the world that
// there will be one more waiting thread now.
if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) {
data->waitingThreads++;
}
-
+
// Unlock
MUTEX_UNLOCK(data->mutex);
@@ -1010,7 +1278,7 @@
return -1;
}
- // Need to wait for availability of underlying layer, then retry.
+ // Need to wait for availability of underlying layer, then retry.
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE: {
int selectResult = sslSelect(error, fd, data, timeout);
@@ -1021,7 +1289,7 @@
} else if (selectResult == 0) {
return THROW_SOCKETTIMEOUTEXCEPTION;
}
-
+
break;
}
@@ -1033,16 +1301,16 @@
if (result == 0) {
return -1;
}
-
+
// System call has been interrupted. Simply retry.
if (errno == EINTR) {
break;
}
-
+
// Note that for all other system call errors we fall through
- // to the default case, which results in an Exception.
+ // to the default case, which results in an Exception.
}
-
+
// Everything else is basically an error.
default: {
*sslReturnCode = result;
@@ -1051,14 +1319,14 @@
}
}
}
-
+
return -1;
}
/**
* Helper function which does the actual writing. The Java layer guarantees that
* at most one thread will enter this function at any given time.
- *
+ *
* @param ssl non-null; the SSL context
* @param buf non-null; buffer to write
* @param len length of the buffer, in bytes
@@ -1069,29 +1337,29 @@
*/
static int sslWrite(SSL* ssl, const char* buf, jint len, int* sslReturnCode,
int* sslErrorCode) {
-
+
// LOGD("Entering sslWrite(), caller requests to write %d bytes...", len);
if (len == 0) {
// Don't bother doing anything in this case.
return 0;
}
-
+
int fd = SSL_get_fd(ssl);
BIO *bio = SSL_get_wbio(ssl);
-
+
APP_DATA* data = (APP_DATA*) SSL_get_app_data(ssl);
-
+
int count = len;
-
- while(data->aliveAndKicking && len > 0) {
+
+ while (data->aliveAndKicking && len > 0) {
errno = 0;
if (MUTEX_LOCK(data->mutex) == -1) {
return -1;
}
-
+
unsigned int bytesMoved = BIO_number_read(bio) + BIO_number_written(bio);
-
+
// LOGD("Doing SSL_write() with %d bytes to go", len);
int result = SSL_write(ssl, buf, len);
int error = SSL_ERROR_NONE;
@@ -1107,15 +1375,15 @@
if (BIO_number_read(bio) + BIO_number_written(bio) != bytesMoved && data->waitingThreads > 0) {
sslNotify(data);
}
-
+
// If we are blocked by the underlying socket, tell the world that
// there will be one more waiting thread now.
if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) {
data->waitingThreads++;
}
-
+
MUTEX_UNLOCK(data->mutex);
-
+
switch (error) {
// Sucessfully write at least one byte.
case SSL_ERROR_NONE: {
@@ -1128,7 +1396,7 @@
case SSL_ERROR_ZERO_RETURN: {
return -1;
}
-
+
// Need to wait for availability of underlying layer, then retry.
// The concept of a write timeout doesn't really make sense, and
// it's also not standard Java behavior, so we wait forever here.
@@ -1142,7 +1410,7 @@
} else if (selectResult == 0) {
return THROW_SOCKETTIMEOUTEXCEPTION;
}
-
+
break;
}
@@ -1154,16 +1422,16 @@
if (result == 0) {
return -1;
}
-
+
// System call has been interrupted. Simply retry.
if (errno == EINTR) {
break;
}
-
+
// Note that for all other system call errors we fall through
- // to the default case, which results in an Exception.
+ // to the default case, which results in an Exception.
}
-
+
// Everything else is basically an error.
default: {
*sslReturnCode = result;
@@ -1173,20 +1441,20 @@
}
}
// LOGD("Successfully wrote %d bytes", count);
-
+
return count;
}
/**
* Helper function that creates an RSA public key from two buffers containing
* the big-endian bit representation of the modulus and the public exponent.
- *
+ *
* @param mod The data of the modulus
* @param modLen The length of the modulus data
* @param exp The data of the exponent
* @param expLen The length of the exponent data
- *
- * @return A pointer to the new RSA structure, or NULL on error
+ *
+ * @return A pointer to the new RSA structure, or NULL on error
*/
static RSA* rsaCreateKey(unsigned char* mod, int modLen, unsigned char* exp, int expLen) {
// LOGD("Entering rsaCreateKey()");
@@ -1207,7 +1475,7 @@
/**
* Helper function that frees an RSA key. Just calls the corresponding OpenSSL
* function.
- *
+ *
* @param rsa The pointer to the new RSA structure to free.
*/
static void rsaFreeKey(RSA* rsa) {
@@ -1220,16 +1488,16 @@
/**
* Helper function that verifies a given RSA signature for a given message.
- *
+ *
* @param msg The message to verify
* @param msgLen The length of the message
* @param sig The signature to verify
* @param sigLen The length of the signature
* @param algorithm The name of the hash/sign algorithm to use, e.g. "RSA-SHA1"
* @param rsa The RSA public key to use
- *
+ *
* @return 1 on success, 0 on failure, -1 on error (check SSL errors then)
- *
+ *
*/
static int rsaVerify(unsigned char* msg, unsigned int msgLen, unsigned char* sig,
unsigned int sigLen, char* algorithm, RSA* rsa) {
@@ -1248,7 +1516,7 @@
EVP_MD_CTX ctx;
- EVP_MD_CTX_init(&ctx);
+ EVP_MD_CTX_init(&ctx);
if (EVP_VerifyInit_ex(&ctx, type, NULL) == 0) {
goto cleanup;
}
@@ -1271,163 +1539,21 @@
// ============================================================================
/**
- * Initialization phase for every OpenSSL job: Loads the Error strings, the
- * crypto algorithms and reset the OpenSSL library
- */
-static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_initstatic(JNIEnv* env, jobject obj)
-{
- SSL_load_error_strings();
- ERR_load_crypto_strings();
- SSL_library_init();
- OpenSSL_add_all_algorithms();
- THREAD_setup();
-}
-
-/**
- * Initialization phase for a socket with OpenSSL. The server's private key
- * and X509 certificate are read and the Linux /dev/urandom file is loaded
- * as RNG for the session keys.
- *
- */
-static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_init(JNIEnv* env, jobject object,
- jstring privatekey, jstring certificates, jbyteArray seed)
-{
- SSL_CTX* ssl_ctx;
-
- // 'seed == null' when no SecureRandom Object is set
- // in the SSLContext.
- if (seed != NULL) {
- jbyte* randseed = env->GetByteArrayElements(seed, NULL);
- RAND_seed((unsigned char*) randseed, 1024);
- env->ReleaseByteArrayElements(seed, randseed, 0);
- } else {
- RAND_load_file("/dev/urandom", 1024);
- }
-
- ssl_ctx = SSL_CTX_new(SSLv23_client_method());
-
- // Note: We explicitly do not allow SSLv2 to be used. It
- SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2);
-
- /* Java code in class OpenSSLSocketImpl does the verification. Meaning of
- * SSL_VERIFY_NONE flag in client mode: if not using an anonymous cipher
- * (by default disabled), the server will send a certificate which will
- * be checked. The result of the certificate verification process can be
- * checked after the TLS/SSL handshake using the SSL_get_verify_result(3)
- * function. The handshake will be continued regardless of the
- * verification result.
- */
- SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL);
-
- int mode = SSL_CTX_get_mode(ssl_ctx);
- /*
- * Turn on "partial write" mode. This means that SSL_write() will
- * behave like Posix write() and possibly return after only
- * writing a partial buffer. Note: The alternative, perhaps
- * surprisingly, is not that SSL_write() always does full writes
- * but that it will force you to retry write calls having
- * preserved the full state of the original call. (This is icky
- * and undesirable.)
- */
- mode |= SSL_MODE_ENABLE_PARTIAL_WRITE;
-#if defined(SSL_MODE_SMALL_BUFFERS) /* not all SSL versions have this */
- mode |= SSL_MODE_SMALL_BUFFERS; /* lazily allocate record buffers; usually saves
- * 44k over the default */
-#endif
-#if defined(SSL_MODE_HANDSHAKE_CUTTHROUGH) /* not all SSL versions have this */
- mode |= SSL_MODE_HANDSHAKE_CUTTHROUGH; /* enable sending of client data as soon as
- * ClientCCS and ClientFinished are sent */
-#endif
-
- SSL_CTX_set_mode(ssl_ctx, mode);
-
- if (privatekey != NULL) {
- BIO* privatekeybio = stringToMemBuf(env, (jstring) privatekey);
- EVP_PKEY* privatekeyevp =
- PEM_read_bio_PrivateKey(privatekeybio, NULL, 0, NULL);
- BIO_free(privatekeybio);
-
- if (privatekeyevp == NULL) {
- throwIOExceptionWithSslErrors(env, 0, 0,
- "Error parsing the private key");
- SSL_CTX_free(ssl_ctx);
- return;
- }
-
- BIO* certificatesbio = stringToMemBuf(env, (jstring) certificates);
- X509* certificatesx509 =
- PEM_read_bio_X509(certificatesbio, NULL, 0, NULL);
- BIO_free(certificatesbio);
-
- if (certificatesx509 == NULL) {
- throwIOExceptionWithSslErrors(env, 0, 0,
- "Error parsing the certificates");
- EVP_PKEY_free(privatekeyevp);
- SSL_CTX_free(ssl_ctx);
- return;
- }
-
- int ret = SSL_CTX_use_certificate(ssl_ctx, certificatesx509);
- if (ret != 1) {
- throwIOExceptionWithSslErrors(env, ret, 0,
- "Error setting the certificates");
- X509_free(certificatesx509);
- EVP_PKEY_free(privatekeyevp);
- SSL_CTX_free(ssl_ctx);
- return;
- }
-
- ret = SSL_CTX_use_PrivateKey(ssl_ctx, privatekeyevp);
- if (ret != 1) {
- throwIOExceptionWithSslErrors(env, ret, 0,
- "Error setting the private key");
- X509_free(certificatesx509);
- EVP_PKEY_free(privatekeyevp);
- SSL_CTX_free(ssl_ctx);
- return;
- }
-
- ret = SSL_CTX_check_private_key(ssl_ctx);
- if (ret != 1) {
- throwIOExceptionWithSslErrors(env, ret, 0,
- "Error checking the private key");
- X509_free(certificatesx509);
- EVP_PKEY_free(privatekeyevp);
- SSL_CTX_free(ssl_ctx);
- return;
- }
- }
-
- env->SetIntField(object, field_Socket_ssl_ctx, (int)ssl_ctx);
-}
-
-/**
* A connection within an OpenSSL context is established. (1) A new socket is
- * constructed, (2) the TLS/SSL handshake with a server is initiated.
+ * constructed, (2) the TLS/SSL handshake with a server is initiated.
*/
-static jboolean org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_connect(JNIEnv* env, jobject object,
- jint ctx, jobject socketObject, jboolean client_mode, jint session)
+static jboolean org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_connect(JNIEnv* env, jclass,
+ jint ssl_address, jobject socketObject, jint timeout, jboolean client_mode, jint ssl_session_address)
{
// LOGD("ENTER connect");
- int ret, fd;
- SSL_CTX* ssl_ctx;
- SSL* ssl;
- SSL_SESSION* ssl_session;
-
- ssl_ctx = (SSL_CTX*)env->GetIntField(object, field_Socket_ssl_ctx);
-
- ssl = create_ssl(env, object, ssl_ctx);
+ SSL* ssl = getSslPointer(env, ssl_address, true);
if (ssl == NULL) {
- throwIOExceptionWithSslErrors(env, 0, 0,
- "Unable to create SSL structure");
- free_ssl_ctx(env, object);
- return (jboolean) false;
+ return (jboolean) false;
}
+ SSL_SESSION* ssl_session = reinterpret_cast<SSL_SESSION*>(static_cast<uintptr_t>(ssl_session_address));
jobject socketImplObject = env->GetObjectField(socketObject, field_Socket_mImpl);
if (socketImplObject == NULL) {
- free_ssl(env, object);
- free_ssl_ctx(env, object);
throwIOExceptionStr(env,
"couldn't get the socket impl from the socket");
return (jboolean) false;
@@ -1435,30 +1561,25 @@
jobject fdObject = env->GetObjectField(socketImplObject, field_Socket_mFD);
if (fdObject == NULL) {
- free_ssl(env, object);
- free_ssl_ctx(env, object);
throwIOExceptionStr(env,
"couldn't get the file descriptor from the socket impl");
return (jboolean) false;
}
- fd = jniGetFDFromFileDescriptor(env, fdObject);
+ int fd = jniGetFDFromFileDescriptor(env, fdObject);
- ssl_session = (SSL_SESSION *) session;
-
- ret = SSL_set_fd(ssl, fd);
+ int ret = SSL_set_fd(ssl, fd);
if (ret != 1) {
throwIOExceptionWithSslErrors(env, ret, 0,
"Error setting the file descriptor");
- free_ssl(env, object);
- free_ssl_ctx(env, object);
+ SSL_clear(ssl);
return (jboolean) false;
}
if (ssl_session != NULL) {
+ // LOGD("Trying to reuse session %p", ssl_session);
ret = SSL_set_session(ssl, ssl_session);
-
if (ret != 1) {
/*
* Translate the error, and throw if it turns out to be a real
@@ -1468,8 +1589,7 @@
if (sslErrorCode != SSL_ERROR_ZERO_RETURN) {
throwIOExceptionWithSslErrors(env, ret, sslErrorCode,
"SSL session set");
- free_ssl(env, object);
- free_ssl_ctx(env, object);
+ SSL_clear(ssl);
return (jboolean) false;
}
}
@@ -1482,8 +1602,7 @@
int mode = fcntl(fd, F_GETFL);
if (mode == -1 || fcntl(fd, F_SETFL, mode | O_NONBLOCK) == -1) {
throwIOExceptionStr(env, "Unable to make socket non blocking");
- free_ssl(env, object);
- free_ssl_ctx(env, object);
+ SSL_clear(ssl);
return (jboolean) false;
}
@@ -1492,19 +1611,15 @@
*/
if (sslCreateAppData(ssl) == -1) {
throwIOExceptionStr(env, "Unable to create application data");
- free_ssl(env, object);
- free_ssl_ctx(env, object);
+ SSL_clear(ssl);
// TODO
return (jboolean) false;
}
-
+
APP_DATA* data = (APP_DATA*) SSL_get_app_data(ssl);
- env->SetIntField(object, field_Socket_ssl, (int)ssl);
-
- int timeout = (int)env->GetIntField(object, field_Socket_timeout);
-
+
while (data->aliveAndKicking) {
- errno = 0;
+ errno = 0;
ret = SSL_connect(ssl);
if (ret == 1) {
break;
@@ -1525,26 +1640,24 @@
if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) {
data->waitingThreads++;
int selectResult = sslSelect(error, fd, data, timeout);
-
+
if (selectResult == -1) {
throwIOExceptionWithSslErrors(env, -1, error,
"Connect error");
- free_ssl(env, object);
- free_ssl_ctx(env, object);
+ SSL_clear(ssl);
return (jboolean) false;
} else if (selectResult == 0) {
throwSocketTimeoutException(env, "SSL handshake timed out");
+ SSL_clear(ssl);
freeSslErrorState();
- free_ssl(env, object);
- free_ssl_ctx(env, object);
return (jboolean) false;
}
} else {
LOGE("Unknown error %d during connect", error);
break;
}
- }
- }
+ }
+ }
if (ret != 1) {
/*
@@ -1555,80 +1668,72 @@
if (sslErrorCode != SSL_ERROR_ZERO_RETURN) {
throwIOExceptionWithSslErrors(env, ret, sslErrorCode,
"SSL handshake failure");
- free_ssl(env, object);
- free_ssl_ctx(env, object);
+ SSL_clear(ssl);
return (jboolean) false;
}
}
if (ssl_session != NULL) {
ret = SSL_session_reused(ssl);
- // if (ret == 1) LOGD("A session was reused");
- // else LOGD("A new session was negotiated");
+ // if (ret == 1) LOGD("Session %p was reused", ssl_session);
+ // else LOGD("Session %p was not reused, using new session %p", ssl_session, SSL_get_session(ssl));
return (jboolean) ret;
} else {
- // LOGD("A new session was negotiated");
+ // LOGD("New session %p was negotiated", SSL_get_session(ssl));
return (jboolean) 0;
}
// LOGD("LEAVE connect");
}
-static jint org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_getsslsession(JNIEnv* env, jobject object,
+static jint org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_getsslsession(JNIEnv* env, jclass,
jint jssl)
{
- return (jint) SSL_get1_session((SSL *) jssl);
+ return (jint) SSL_get1_session((SSL*) jssl);
}
-static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_accept(JNIEnv* env, jobject object,
- jobject socketObject, jint jssl_ctx, jboolean client_mode)
+static jint org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_accept(JNIEnv* env, jclass,
+ jint ssl_address, jobject socketObject)
{
- int sd, ret;
- BIO *bio;
- SSL *ssl;
- SSL_CTX *ssl_ctx;
- jsse_ssl_app_data_t appdata;
+ SSL* serverSocketSsl = reinterpret_cast<SSL*>(static_cast<uintptr_t>(ssl_address));
+ if (serverSocketSsl == NULL) {
+ throwIOExceptionWithSslErrors(env, 0, 0,
+ "Unusable SSL structure");
+ return NULL;
+ }
- ssl_ctx = (SSL_CTX *)jssl_ctx;
-
- ssl = create_ssl(env, object, ssl_ctx);
+ SSL* ssl = SSL_dup(serverSocketSsl);
if (ssl == NULL) {
throwIOExceptionWithSslErrors(env, 0, 0,
"Unable to create SSL structure");
- return;
+ return NULL;
}
jobject socketImplObject = env->GetObjectField(socketObject, field_Socket_mImpl);
if (socketImplObject == NULL) {
- free_ssl(env, object);
throwIOExceptionStr(env, "couldn't get the socket impl from the socket");
- return;
+ return NULL;
}
jobject fdObject = env->GetObjectField(socketImplObject, field_Socket_mFD);
if (fdObject == NULL) {
- free_ssl(env, object);
throwIOExceptionStr(env, "couldn't get the file descriptor from the socket impl");
- return;
+ return NULL;
}
- sd = jniGetFDFromFileDescriptor(env, fdObject);
+ int sd = jniGetFDFromFileDescriptor(env, fdObject);
- bio = BIO_new_socket(sd, BIO_NOCLOSE);
-
- /* The parameter client_mode must be 1 */
- if (client_mode != 0)
- client_mode = 1;
- BIO_set_ssl_mode(bio, client_mode);
-
+ BIO* bio = BIO_new_socket(sd, BIO_NOCLOSE);
SSL_set_bio(ssl, bio, bio);
/*
- * Fill in the appdata structure needed for the certificate callback and
- * store this in the SSL application data slot.
+ * Fill in the stack allocated appdata structure needed for the
+ * certificate callback and store this in the SSL application data
+ * slot.
*/
+ jsse_ssl_app_data_t appdata;
appdata.env = env;
- appdata.object = object;
+ appdata.object = socketObject;
SSL_set_app_data(ssl, &appdata);
/*
@@ -1636,7 +1741,7 @@
* Maybe we need to deal with all the special SSL error cases (WANT_*),
* just like we do for SSL_connect(). But currently it is looking ok.
*/
- ret = SSL_accept(ssl);
+ int ret = SSL_accept(ssl);
/*
* Clear the SSL application data slot again, so we can safely use it for
@@ -1654,14 +1759,14 @@
*/
int sslErrorCode = SSL_get_error(ssl, ret);
if (sslErrorCode == SSL_ERROR_NONE ||
- (sslErrorCode == SSL_ERROR_SYSCALL && errno == 0)) {
- throwIOExceptionStr(env, "Connection closed by peer");
+ (sslErrorCode == SSL_ERROR_SYSCALL && errno == 0)) {
+ throwIOExceptionStr(env, "Connection closed by peer");
} else {
- throwIOExceptionWithSslErrors(env, ret, sslErrorCode,
- "Trouble accepting connection");
+ throwIOExceptionWithSslErrors(env, ret, sslErrorCode,
+ "Trouble accepting connection");
}
- free_ssl(env, object);
- return;
+ SSL_clear(ssl);
+ return NULL;
} else if (ret < 0) {
/*
* Translate the error and throw exception. We are sure it is an error
@@ -1670,8 +1775,8 @@
int sslErrorCode = SSL_get_error(ssl, ret);
throwIOExceptionWithSslErrors(env, ret, sslErrorCode,
"Trouble accepting connection");
- free_ssl(env, object);
- return;
+ SSL_clear(ssl);
+ return NULL;
}
/*
@@ -1682,8 +1787,8 @@
int mode = fcntl(fd, F_GETFL);
if (mode == -1 || fcntl(fd, F_SETFL, mode | O_NONBLOCK) == -1) {
throwIOExceptionStr(env, "Unable to make socket non blocking");
- free_ssl(env, object);
- return;
+ SSL_clear(ssl);
+ return NULL;
}
/*
@@ -1691,114 +1796,11 @@
*/
if (sslCreateAppData(ssl) == -1) {
throwIOExceptionStr(env, "Unable to create application data");
- free_ssl(env, object);
- return;
- }
-}
-
-/**
- * Loads the desired protocol for the OpenSSL client and enables it.
- * For example SSL_OP_NO_TLSv1 means do not use TLS v. 1.
- */
-static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_setenabledprotocols(JNIEnv* env, jobject object,
- jlong protocol)
-{
- if (protocol != 0x00000000L) {
- if (protocol & SSL_OP_NO_SSLv3)
- LOGD("SSL_OP_NO_SSLv3 is set");
- if (protocol & SSL_OP_NO_TLSv1)
- LOGD("SSL_OP_NO_TLSv1 is set");
-
- SSL_CTX* ctx = (SSL_CTX*)env->GetIntField(object, field_Socket_ssl_ctx);
- int options = SSL_CTX_get_options(ctx);
- options |= protocol; // Note: SSLv2 disabled earlier.
- SSL_CTX_set_options(ctx, options);
- }
-}
-
-static jobjectArray makeCipherList(JNIEnv* env, SSL* ssl) {
- // Count the ciphers.
- int cipherCount = 0;
- while (SSL_get_cipher_list(ssl, cipherCount) != NULL) {
- ++cipherCount;
- }
-
- // Create a String[].
- jclass stringClass = env->FindClass("java/lang/String");
- if (stringClass == NULL) {
- return NULL;
- }
- jobjectArray array = env->NewObjectArray(cipherCount, stringClass, NULL);
- if (array == NULL) {
+ SSL_clear(ssl);
return NULL;
}
- // Fill in the cipher names.
- for (int i = 0; i < cipherCount; ++i) {
- const char* c = SSL_get_cipher_list(ssl, i);
- env->SetObjectArrayElement(array, i, env->NewStringUTF(c));
- }
- return array;
-}
-
-jobjectArray makeCipherList(JNIEnv* env, SSL_CTX* ssl_ctx) {
- SSL* ssl = SSL_new(ssl_ctx);
- if (ssl == NULL) {
- return NULL;
- }
- jobjectArray result = makeCipherList(env, ssl);
- SSL_free(ssl);
- return result;
-}
-
-/**
- * Loads the ciphers suites that are supported by the OpenSSL client
- * and returns them in a string array.
- */
-static jobjectArray org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_getsupportedciphersuites(JNIEnv* env,
- jobject object)
-{
- SSL_CTX* ssl_ctx = SSL_CTX_new(SSLv23_client_method());
- if (ssl_ctx == NULL) {
- return NULL;
- }
- jobjectArray result = makeCipherList(env, ssl_ctx);
- SSL_CTX_free(ssl_ctx);
- return result;
-}
-
-/**
- * Loads the ciphers suites that are enabled in the OpenSSL client
- * and returns them in a string array.
- */
-static jobjectArray OpenSSLSocketImpl_nativeGetEnabledCipherSuites(JNIEnv* env,
- jclass, jint ssl_ctx_address)
-{
- SSL_CTX* ssl_ctx =
- reinterpret_cast<SSL_CTX*>(static_cast<uintptr_t>(ssl_ctx_address));
- return makeCipherList(env, ssl_ctx);
-}
-
-void setEnabledCipherSuites(JNIEnv* env, jstring controlString, SSL_CTX* ssl_ctx) {
- const char* str = env->GetStringUTFChars(controlString, NULL);
- int rc = SSL_CTX_set_cipher_list(ssl_ctx, str);
- env->ReleaseStringUTFChars(controlString, str);
- if (rc == 0) {
- freeSslErrorState();
- jniThrowException(env, "java/lang/IllegalArgumentException",
- "Illegal cipher suite strings.");
- }
-}
-
-/**
- * Sets the ciphers suites that are enabled in the OpenSSL client.
- */
-static void OpenSSLSocketImpl_nativeSetEnabledCipherSuites(JNIEnv* env, jclass,
- jint ssl_ctx_address, jstring controlString)
-{
- SSL_CTX* ssl_ctx =
- reinterpret_cast<SSL_CTX*>(static_cast<uintptr_t>(ssl_ctx_address));
- setEnabledCipherSuites(env, controlString, ssl_ctx);
+ return (jint) ssl;
}
#define SSL_AUTH_MASK 0x00007F00L
@@ -1815,24 +1817,18 @@
* Sets the client's crypto algorithms and authentication methods.
*/
static jstring org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_cipherauthenticationmethod(JNIEnv* env,
- jobject object)
+ jclass, jint ssl_address)
{
- SSL* ssl;
- SSL_CIPHER *cipher;
- jstring ret;
- char buf[512];
- unsigned long alg;
- const char *au;
-
- ssl = getSslPointer(env, object, true);
+ SSL* ssl = getSslPointer(env, ssl_address, true);
if (ssl == NULL) {
return NULL;
}
- cipher = SSL_get_current_cipher(ssl);
+ SSL_CIPHER* cipher = SSL_get_current_cipher(ssl);
- alg = cipher->algorithms;
+ unsigned long alg = cipher->algorithms;
+ const char *au;
switch (alg&SSL_AUTH_MASK) {
case SSL_aRSA:
au="RSA";
@@ -1857,7 +1853,7 @@
break;
}
- ret = env->NewStringUTF(au);
+ jstring ret = env->NewStringUTF(au);
return ret;
}
@@ -1865,9 +1861,9 @@
/**
* OpenSSL read function (1): only one chunk is read (returned as jint).
*/
-static jint org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_read(JNIEnv* env, jobject object, jint timeout)
+static jint org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_read(JNIEnv* env, jclass, jint ssl_address, jint timeout)
{
- SSL *ssl = getSslPointer(env, object, true);
+ SSL* ssl = getSslPointer(env, ssl_address, true);
if (ssl == NULL) {
return 0;
}
@@ -1897,12 +1893,12 @@
}
/**
- * OpenSSL read function (2): read into buffer at offset n chunks.
+ * OpenSSL read function (2): read into buffer at offset n chunks.
* Returns 1 (success) or value <= 0 (failure).
*/
-static jint org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_readba(JNIEnv* env, jobject obj, jbyteArray dest, jint offset, jint len, jint timeout)
+static jint org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_readba(JNIEnv* env, jclass, jint ssl_address, jbyteArray dest, jint offset, jint len, jint timeout)
{
- SSL *ssl = getSslPointer(env, obj, true);
+ SSL* ssl = getSslPointer(env, ssl_address, true);
if (ssl == NULL) {
return 0;
}
@@ -1932,9 +1928,9 @@
/**
* OpenSSL write function (1): only one chunk is written.
*/
-static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_write(JNIEnv* env, jobject object, jint b)
+static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_write(JNIEnv* env, jclass, jint ssl_address, jint b)
{
- SSL *ssl = getSslPointer(env, object, true);
+ SSL* ssl = getSslPointer(env, ssl_address, true);
if (ssl == NULL) {
return;
}
@@ -1954,12 +1950,12 @@
}
/**
- * OpenSSL write function (2): write into buffer at offset n chunks.
+ * OpenSSL write function (2): write into buffer at offset n chunks.
*/
-static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_writeba(JNIEnv* env, jobject obj,
- jbyteArray dest, jint offset, jint len)
+static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_writeba(JNIEnv* env, jclass,
+ jint ssl_address, jbyteArray dest, jint offset, jint len)
{
- SSL *ssl = getSslPointer(env, obj, true);
+ SSL* ssl = getSslPointer(env, ssl_address, true);
if (ssl == NULL) {
return;
}
@@ -1967,8 +1963,7 @@
jbyte* bytes = env->GetByteArrayElements(dest, NULL);
int returnCode = 0;
int errorCode = 0;
- int timeout = (int)env->GetIntField(obj, field_Socket_timeout);
- int ret = sslWrite(ssl, (const char *) (bytes + offset), len,
+ int ret = sslWrite(ssl, (const char *) (bytes + offset), len,
&returnCode, &errorCode);
env->ReleaseByteArrayElements(dest, bytes, 0);
@@ -1983,11 +1978,11 @@
}
/**
- * Interrupt any pending IO before closing the socket.
+ * Interrupt any pending IO before closing the socket.
*/
static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_interrupt(
- JNIEnv* env, jobject object) {
- SSL *ssl = getSslPointer(env, object, false);
+ JNIEnv* env, jclass, jint ssl_address) {
+ SSL* ssl = getSslPointer(env, ssl_address, false);
if (ssl == NULL) {
return;
}
@@ -2007,11 +2002,11 @@
}
/**
- * OpenSSL close SSL socket function.
+ * OpenSSL close SSL socket function.
*/
static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_close(
- JNIEnv* env, jobject object) {
- SSL *ssl = getSslPointer(env, object, false);
+ JNIEnv* env, jclass, jint ssl_address) {
+ SSL* ssl = getSslPointer(env, ssl_address, false);
if (ssl == NULL) {
return;
}
@@ -2051,23 +2046,13 @@
* Everything else is a real error condition. We should
* let the Java layer know about this by throwing an
* exception.
- */
+ */
throwIOExceptionWithSslErrors(env, ret, 0, "SSL shutdown failed.");
break;
}
+ SSL_clear(ssl);
freeSslErrorState();
- free_ssl(env, object);
- free_ssl_ctx(env, object);
-}
-
-/**
- * OpenSSL free SSL socket function.
- */
-static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_free(JNIEnv* env, jobject object)
-{
- free_ssl(env, object);
- free_ssl_ctx(env, object);
}
/**
@@ -2130,151 +2115,20 @@
static JNINativeMethod sSocketImplMethods[] =
{
- {"nativeinitstatic", "()V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_initstatic},
- {"nativeinit", "(Ljava/lang/String;Ljava/lang/String;[B)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_init},
- {"nativeconnect", "(ILjava/net/Socket;ZI)Z", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_connect},
+ {"nativeconnect", "(ILjava/net/Socket;IZI)Z", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_connect},
{"nativegetsslsession", "(I)I", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_getsslsession},
- {"nativeread", "(I)I", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_read},
- {"nativeread", "([BIII)I", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_readba},
- {"nativewrite", "(I)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_write},
- {"nativewrite", "([BII)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_writeba},
- {"nativeaccept", "(Ljava/net/Socket;IZ)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_accept},
- {"nativesetenabledprotocols", "(J)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_setenabledprotocols},
- {"nativegetsupportedciphersuites", "()[Ljava/lang/String;", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_getsupportedciphersuites},
- {"nativeGetEnabledCipherSuites", "(I)[Ljava/lang/String;", (void*) OpenSSLSocketImpl_nativeGetEnabledCipherSuites},
- {"nativeSetEnabledCipherSuites", "(ILjava/lang/String;)V", (void*) OpenSSLSocketImpl_nativeSetEnabledCipherSuites},
- {"nativecipherauthenticationmethod", "()Ljava/lang/String;", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_cipherauthenticationmethod},
- {"nativeinterrupt", "()V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_interrupt},
- {"nativeclose", "()V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_close},
- {"nativefree", "()V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_free},
+ {"nativeread", "(II)I", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_read},
+ {"nativeread", "(I[BIII)I", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_readba},
+ {"nativewrite", "(II)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_write},
+ {"nativewrite", "(I[BII)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_writeba},
+ {"nativeaccept", "(ILjava/net/Socket;)I", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_accept},
+ {"nativecipherauthenticationmethod", "(I)Ljava/lang/String;", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_cipherauthenticationmethod},
+ {"nativeinterrupt", "(I)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_interrupt},
+ {"nativeclose", "(I)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_close},
{"nativeverifysignature", "([B[BLjava/lang/String;[B[B)I", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_verifysignature},
};
/**
- * Module scope variables initialized during JNI registration.
- */
-static jfieldID field_ServerSocket_ssl_ctx;
-
-/**
- * Initialization phase of OpenSSL: Loads the Error strings, the crypto algorithms and reset the OpenSSL library
- */
-static void org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_initstatic(JNIEnv* env, jobject obj)
-{
- SSL_load_error_strings();
- ERR_load_crypto_strings();
- SSL_library_init();
- OpenSSL_add_all_algorithms();
-}
-
-/**
- * Initialization phase for a server socket with OpenSSL. The server's private key and X509 certificate are read and
- * the Linux /dev/random file is loaded as RNG for the session keys.
- *
- */
-static void org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_init(JNIEnv* env, jobject object,
- jstring privatekey, jstring certificates, jbyteArray seed)
-{
- SSL_CTX *ssl_ctx;
- const char *privatekeychar;
- const char *certificateschar;
- EVP_PKEY * privatekeyevp;
-
- BIO *privatekeybio;
- BIO *certificatesbio;
-
- // 'seed == null' when no SecureRandom Object is set
- // in the SSLContext.
- if (seed != NULL) {
- jbyte* randseed = env->GetByteArrayElements(seed, NULL);
- RAND_seed((unsigned char*) randseed, 1024);
- env->ReleaseByteArrayElements(seed, randseed, 0);
- } else {
- RAND_load_file("/dev/urandom", 1024);
- }
-
- ssl_ctx = SSL_CTX_new(SSLv23_server_method());
- SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL|SSL_OP_NO_SSLv2);
-
- privatekeychar = env->GetStringUTFChars((jstring)privatekey, NULL);
- privatekeybio = BIO_new_mem_buf((void*)privatekeychar, -1);
-
- privatekeyevp = PEM_read_bio_PrivateKey(privatekeybio, NULL, 0, NULL);
- env->ReleaseStringUTFChars(privatekey, privatekeychar);
-
- if (privatekeyevp == NULL) {
- LOGE(ERR_error_string(ERR_get_error(), NULL));
- throwIOExceptionStr(env, "Error parsing the private key");
- return;
- }
-
- certificateschar = env->GetStringUTFChars((jstring)certificates, NULL);
- certificatesbio = BIO_new_mem_buf((void*)certificateschar, -1);
-
- X509 * certificatesx509 = PEM_read_bio_X509(certificatesbio, NULL, 0, NULL);
- env->ReleaseStringUTFChars(certificates, certificateschar);
-
- if (certificatesx509 == NULL) {
- LOGE(ERR_error_string(ERR_get_error(), NULL));
- throwIOExceptionStr(env, "Error parsing the certificates");
- return;
- }
-
- if (!SSL_CTX_use_certificate(ssl_ctx, certificatesx509)) {
- LOGE(ERR_error_string(ERR_get_error(), NULL));
- throwIOExceptionStr(env, "Error setting the certificates");
- return;
- }
-
- if (!SSL_CTX_use_PrivateKey(ssl_ctx, privatekeyevp)) {
- LOGE(ERR_error_string(ERR_get_error(), NULL));
- throwIOExceptionStr(env, "Error setting the private key");
- return;
- }
-
- if (!SSL_CTX_check_private_key(ssl_ctx)) {
- LOGE(ERR_error_string(ERR_get_error(), NULL));
- throwIOExceptionStr(env, "Error checking private key");
- return;
- }
-
- env->SetIntField(object, field_ServerSocket_ssl_ctx, (int)ssl_ctx);
-}
-
-/**
- * Loads the desired protocol for the OpenSSL server and enables it.
- * For example SSL_OP_NO_TLSv1 means do not use TLS v. 1.
- */
-static void org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_setenabledprotocols(JNIEnv* env,
- jobject object, jlong protocol)
-{
- if (protocol != 0x00000000L) {
- if (protocol & SSL_OP_NO_SSLv3)
- LOGD("SSL_OP_NO_SSLv3 is set");
- if (protocol & SSL_OP_NO_TLSv1)
- LOGD("SSL_OP_NO_TLSv1 is set");
-
- SSL_CTX* ctx = (SSL_CTX*)env->GetIntField(object, field_ServerSocket_ssl_ctx);
- SSL_CTX_set_options((SSL_CTX*)ctx, SSL_OP_ALL|SSL_OP_NO_SSLv2|(long)protocol);
- }
-}
-
-/**
- * Loads the ciphers suites that are supported by the OpenSSL server
- * and returns them in a string array.
- */
-static jobjectArray org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_getsupportedciphersuites(JNIEnv* env,
- jobject object)
-{
- SSL_CTX* ssl_ctx = SSL_CTX_new(SSLv23_server_method());
- if (ssl_ctx == NULL) {
- return NULL;
- }
- jobjectArray result = makeCipherList(env, ssl_ctx);
- SSL_CTX_free(ssl_ctx);
- return result;
-}
-
-/**
* Gives an array back containing all the X509 certificate's bytes.
*/
static jobjectArray getcertificatebytes(JNIEnv* env,
@@ -2338,79 +2192,64 @@
*/
static int verify_callback(int preverify_ok, X509_STORE_CTX *x509_store_ctx)
{
- SSL *ssl;
- jsse_ssl_app_data_t *appdata;
- jclass cls;
-
- jobjectArray objectArray;
-
/* Get the correct index to the SSLobject stored into X509_STORE_CTX. */
- ssl = (SSL*)X509_STORE_CTX_get_ex_data(x509_store_ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
+ SSL* ssl = (SSL*)X509_STORE_CTX_get_ex_data(x509_store_ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
- appdata = (jsse_ssl_app_data_t*)SSL_get_app_data(ssl);
+ jsse_ssl_app_data_t* appdata = (jsse_ssl_app_data_t*)SSL_get_app_data(ssl);
- cls = appdata->env->GetObjectClass(appdata->object);
+ jclass cls = appdata->env->GetObjectClass(appdata->object);
- jmethodID methodID = appdata->env->GetMethodID(cls, "verify_callback", "([[B)I");
+ jmethodID methodID = appdata->env->GetMethodID(cls, "verifyCertificateChain", "([[B)Z");
- objectArray = getcertificatebytes(appdata->env, x509_store_ctx->untrusted);
+ jobjectArray objectArray = getcertificatebytes(appdata->env, x509_store_ctx->untrusted);
- appdata->env->CallIntMethod(appdata->object, methodID, objectArray);
+ jboolean verified = appdata->env->CallBooleanMethod(appdata->object, methodID, objectArray);
- return 1;
+ return (verified) ? 1 : 0;
}
/**
* Sets the client's credentials and the depth of theirs verification.
*/
static void org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_nativesetclientauth(JNIEnv* env,
- jobject object, jint value)
+ jclass, jint ssl_address, jint value)
{
- SSL_CTX *ssl_ctx = (SSL_CTX *)env->GetIntField(object, field_ServerSocket_ssl_ctx);
- SSL_CTX_set_verify(ssl_ctx, (int)value, verify_callback);
-}
-
-/**
- * The actual SSL context is reset.
- */
-static void org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_nativefree(JNIEnv* env, jobject object)
-{
- SSL_CTX *ctx = (SSL_CTX *)env->GetIntField(object, field_ServerSocket_ssl_ctx);
- SSL_CTX_free(ctx);
- env->SetIntField(object, field_ServerSocket_ssl_ctx, 0);
+ SSL* ssl = getSslPointer(env, ssl_address, true);
+ if (ssl == NULL) {
+ return;
+ }
+ SSL_set_verify(ssl, (int)value, verify_callback);
}
static JNINativeMethod sServerSocketImplMethods[] =
{
- {"nativeinitstatic", "()V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_initstatic},
- {"nativeinit", "(Ljava/lang/String;Ljava/lang/String;[B)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_init},
- {"nativesetenabledprotocols", "(J)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_setenabledprotocols},
- {"nativegetsupportedciphersuites", "()[Ljava/lang/String;", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_getsupportedciphersuites},
- {"nativesetclientauth", "(I)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_nativesetclientauth},
- {"nativefree", "()V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_nativefree}
+ {"nativesetclientauth", "(II)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_nativesetclientauth},
};
-static jfieldID field_Session_session;
-
-static SSL_SESSION* getSslSessionPointer(JNIEnv* env, jobject object) {
- return reinterpret_cast<SSL_SESSION*>(env->GetIntField(object, field_Session_session));
+/**
+ * Our implementation of what might be considered
+ * SSL_SESSION_get_peer_cert_chain
+ */
+static STACK_OF(X509)* SSL_SESSION_get_peer_cert_chain(SSL_CTX* ssl_ctx, SSL_SESSION* ssl_session) {
+ SSL* ssl = SSL_new(ssl_ctx);
+ SSL_set_session(ssl, ssl_session);
+ STACK_OF(X509)* chain = SSL_get_peer_cert_chain(ssl);
+ SSL_free(ssl);
+ return chain;
}
// Fills a byte[][] with the peer certificates in the chain.
static jobjectArray OpenSSLSessionImpl_getPeerCertificatesImpl(JNIEnv* env,
- jobject object, jint jssl)
+ jclass, jint ssl_ctx_address, jint ssl_session_address)
{
- SSL_SESSION* ssl_session = getSslSessionPointer(env, object);
- SSL_CTX* ssl_ctx = SSL_CTX_new(SSLv23_client_method());
- SSL* ssl = SSL_new(ssl_ctx);
-
- SSL_set_session(ssl, ssl_session);
-
- STACK_OF(X509)* chain = SSL_get_peer_cert_chain(ssl);
+ SSL_CTX* ssl_ctx = reinterpret_cast<SSL_CTX*>(static_cast<uintptr_t>(ssl_ctx_address));
+ if (ssl_ctx == NULL) {
+ jniThrowNullPointerException(env, "SSL_CTX is null");
+ return NULL;
+ }
+ SSL_SESSION* ssl_session = reinterpret_cast<SSL_SESSION*>(static_cast<uintptr_t>(ssl_session_address));
+ STACK_OF(X509)* chain = SSL_SESSION_get_peer_cert_chain(ssl_ctx, ssl_session);
jobjectArray objectArray = getcertificatebytes(env, chain);
-
- SSL_free(ssl);
- SSL_CTX_free(ssl_ctx);
return objectArray;
}
@@ -2419,8 +2258,8 @@
* not certificates). Returns a byte[] containing the DER-encoded state.
* See apache mod_ssl.
*/
-static jbyteArray OpenSSLSessionImpl_getEncoded(JNIEnv* env, jobject object) {
- SSL_SESSION* ssl_session = getSslSessionPointer(env, object);
+static jbyteArray OpenSSLSessionImpl_getEncoded(JNIEnv* env, jclass, jint ssl_session_address) {
+ SSL_SESSION* ssl_session = reinterpret_cast<SSL_SESSION*>(static_cast<uintptr_t>(ssl_session_address));
if (ssl_session == NULL) {
return NULL;
}
@@ -2445,7 +2284,7 @@
/**
* Deserialize the session.
*/
-static jint OpenSSLSessionImpl_initializeNativeImpl(JNIEnv* env, jobject object, jbyteArray bytes, jint size) {
+static jint OpenSSLSessionImpl_initializeNativeImpl(JNIEnv* env, jclass, jbyteArray bytes, jint size) {
if (bytes == NULL) {
return 0;
}
@@ -2461,8 +2300,8 @@
/**
* Gets and returns in a byte array the ID of the actual SSL session.
*/
-static jbyteArray OpenSSLSessionImpl_getId(JNIEnv* env, jobject object) {
- SSL_SESSION* ssl_session = getSslSessionPointer(env, object);
+static jbyteArray OpenSSLSessionImpl_getId(JNIEnv* env, jclass, jint ssl_session_address) {
+ SSL_SESSION* ssl_session = reinterpret_cast<SSL_SESSION*>(static_cast<uintptr_t>(ssl_session_address));
jbyteArray result = env->NewByteArray(ssl_session->session_id_length);
if (result != NULL) {
@@ -2477,68 +2316,61 @@
* Gets and returns in a long integer the creation's time of the
* actual SSL session.
*/
-static jlong OpenSSLSessionImpl_getCreationTime(JNIEnv* env, jobject object) {
- SSL_SESSION* ssl_session = getSslSessionPointer(env, object);
+static jlong OpenSSLSessionImpl_getCreationTime(JNIEnv* env, jclass, jint ssl_session_address) {
+ SSL_SESSION* ssl_session = reinterpret_cast<SSL_SESSION*>(static_cast<uintptr_t>(ssl_session_address));
jlong result = SSL_SESSION_get_time(ssl_session);
result *= 1000; // OpenSSL uses seconds, Java uses milliseconds.
return result;
}
/**
+ * Our implementation of what might be considered
+ * SSL_SESSION_get_version, based on SSL_get_version.
+ * See get_ssl_version above.
+ */
+static const char* SSL_SESSION_get_version(SSL_SESSION* ssl_session) {
+ return get_ssl_version(ssl_session->ssl_version);
+}
+
+/**
* Gets and returns in a string the version of the SSL protocol. If it
* returns the string "unknown" it means that no connection is established.
*/
-static jstring OpenSSLSessionImpl_getProtocol(JNIEnv* env, jobject object) {
- SSL_SESSION* ssl_session = getSslSessionPointer(env, object);
- SSL_CTX* ssl_ctx = SSL_CTX_new(SSLv23_client_method());
- SSL* ssl = SSL_new(ssl_ctx);
-
- SSL_set_session(ssl, ssl_session);
-
- const char* protocol = SSL_get_version(ssl);
+static jstring OpenSSLSessionImpl_getProtocol(JNIEnv* env, jclass, jint ssl_session_address) {
+ SSL_SESSION* ssl_session = reinterpret_cast<SSL_SESSION*>(static_cast<uintptr_t>(ssl_session_address));
+ const char* protocol = SSL_SESSION_get_version(ssl_session);
jstring result = env->NewStringUTF(protocol);
-
- SSL_free(ssl);
- SSL_CTX_free(ssl_ctx);
return result;
}
/**
* Gets and returns in a string the set of ciphers the actual SSL session uses.
*/
-static jstring OpenSSLSessionImpl_getCipherSuite(JNIEnv* env, jobject object) {
- SSL_SESSION* ssl_session = getSslSessionPointer(env, object);
- SSL_CTX* ssl_ctx = SSL_CTX_new(SSLv23_client_method());
- SSL* ssl = SSL_new(ssl_ctx);
-
- SSL_set_session(ssl, ssl_session);
-
- SSL_CIPHER* cipher = SSL_get_current_cipher(ssl);
+static jstring OpenSSLSessionImpl_getCipherSuite(JNIEnv* env, jclass, jint ssl_session_address) {
+ SSL_SESSION* ssl_session = reinterpret_cast<SSL_SESSION*>(static_cast<uintptr_t>(ssl_session_address));
+ SSL_CIPHER* cipher = ssl_session->cipher;
jstring result = env->NewStringUTF(SSL_CIPHER_get_name(cipher));
-
- SSL_free(ssl);
- SSL_CTX_free(ssl_ctx);
return result;
}
/**
* Frees the SSL session.
*/
-static void OpenSSLSessionImpl_freeImpl(JNIEnv* env, jobject object, jint session) {
- LOGD("Freeing OpenSSL session");
+static void OpenSSLSessionImpl_freeImpl(JNIEnv* env, jclass, jint session) {
SSL_SESSION* ssl_session = reinterpret_cast<SSL_SESSION*>(session);
+ // LOGD("Freeing OpenSSL session %p", session);
SSL_SESSION_free(ssl_session);
}
static JNINativeMethod sSessionImplMethods[] = {
{ "freeImpl", "(I)V", (void*) OpenSSLSessionImpl_freeImpl },
- { "getCipherSuite", "()Ljava/lang/String;", (void*) OpenSSLSessionImpl_getCipherSuite },
- { "getCreationTime", "()J", (void*) OpenSSLSessionImpl_getCreationTime },
- { "getEncoded", "()[B", (void*) OpenSSLSessionImpl_getEncoded },
- { "getId", "()[B", (void*) OpenSSLSessionImpl_getId },
- { "getPeerCertificatesImpl", "()[[B", (void*) OpenSSLSessionImpl_getPeerCertificatesImpl },
- { "getProtocol", "()Ljava/lang/String;", (void*) OpenSSLSessionImpl_getProtocol },
- { "initializeNativeImpl", "([BI)I", (void*) OpenSSLSessionImpl_initializeNativeImpl }
+ { "getCipherSuite", "(I)Ljava/lang/String;", (void*) OpenSSLSessionImpl_getCipherSuite },
+ { "getCreationTime", "(I)J", (void*) OpenSSLSessionImpl_getCreationTime },
+ { "getEncoded", "(I)[B", (void*) OpenSSLSessionImpl_getEncoded },
+ { "getId", "(I)[B", (void*) OpenSSLSessionImpl_getId },
+ { "getPeerCertificatesImpl", "(II)[[B", (void*) OpenSSLSessionImpl_getPeerCertificatesImpl },
+ { "getProtocol", "(I)Ljava/lang/String;", (void*) OpenSSLSessionImpl_getProtocol },
+ { "initializeNativeImpl", "([BI)I", (void*) OpenSSLSessionImpl_initializeNativeImpl },
};
typedef struct {
@@ -2573,18 +2405,6 @@
}
}
- // java.io.FileDescriptor
- jclass fileDescriptor = env->FindClass("java/io/FileDescriptor");
- if (fileDescriptor == NULL) {
- LOGE("Can't find java/io/FileDescriptor");
- return -1;
- }
- field_FileDescriptor_descriptor = env->GetFieldID(fileDescriptor, "descriptor", "I");
- if (field_FileDescriptor_descriptor == NULL) {
- LOGE("Can't find FileDescriptor.descriptor");
- return -1;
- }
-
// java.net.Socket
jclass socket = env->FindClass("java/net/Socket");
if (socket == NULL) {
@@ -2609,58 +2429,5 @@
return -1;
}
- // org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl
- jclass socketImpl = env->FindClass("org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl");
- if (socketImpl == NULL) {
- LOGE("Can't find org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl");
- return -1;
- }
- // Note: do these after the registration of native methods, because
- // there is a static method "initstatic" that's called when the
- // OpenSSLSocketImpl class is first loaded, and that required
- // a native method to be associated with it.
- field_Socket_ssl_ctx = env->GetFieldID(socketImpl, "ssl_ctx", "I");
- if (field_Socket_ssl_ctx == NULL) {
- LOGE("Can't find OpenSSLSocketImpl.ssl_ctx");
- return -1;
- }
- field_Socket_ssl = env->GetFieldID(socketImpl, "ssl", "I");
- if (field_Socket_ssl == NULL) {
- LOGE("Can't find OpenSSLSocketImpl.ssl");
- return -1;
- }
- field_Socket_timeout = env->GetFieldID(socketImpl, "timeout", "I");
- if (field_Socket_timeout == NULL) {
- LOGE("Can't find OpenSSLSocketImpl.timeout");
- return -1;
- }
-
- // org.apache.harmony.xnet.provider.jsse.OpenSSLServerSocketImpl
- jclass serverSocketImpl = env->FindClass("org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl");
- if (serverSocketImpl == NULL) {
- LOGE("Can't find org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl");
- return -1;
- }
- // Note: do these after the registration of native methods, because
- // there is a static method "initstatic" that's called when the
- // OpenSSLServerSocketImpl class is first loaded, and that required
- // a native method to be associated with it.
- field_ServerSocket_ssl_ctx = env->GetFieldID(serverSocketImpl, "ssl_ctx", "I");
- if (field_ServerSocket_ssl_ctx == NULL) {
- LOGE("Can't find OpenSSLServerSocketImpl.ssl_ctx");
- return -1;
- }
-
- // org.apache.harmony.xnet.provider.jsse.OpenSSLSessionImpl
- jclass sessionImpl = env->FindClass("org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl");
- if (sessionImpl == NULL) {
- return -1;
- }
- field_Session_session = env->GetFieldID(sessionImpl, "session", "I");
- if (field_Session_session == NULL) {
- LOGE("Can't find OpenSSLSessionImpl.session");
- return -1;
- }
-
return 0;
}
diff --git a/libdex/InstrUtils.h b/libdex/InstrUtils.h
index 0c4e162..60244a8 100644
--- a/libdex/InstrUtils.h
+++ b/libdex/InstrUtils.h
@@ -90,8 +90,8 @@
typedef signed char InstructionWidth;
/*
- * Instruction flags, used by the verifier to determine where control
- * can flow to next.
+ * Instruction flags, used by the verifier and JIT to determine where
+ * control can flow to next. Expected to fit in 8 bits.
*/
typedef unsigned char InstructionFlags;
enum InstructionFlags {
diff --git a/vm/Dalvik.h b/vm/Dalvik.h
index 054b838..27fa316 100644
--- a/vm/Dalvik.h
+++ b/vm/Dalvik.h
@@ -67,7 +67,7 @@
#include "JniInternal.h"
#include "LinearAlloc.h"
#include "analysis/DexVerify.h"
-#include "analysis/DexOptimize.h"
+#include "analysis/DexPrepare.h"
#include "analysis/RegisterMap.h"
#include "Init.h"
#include "libdex/OpCode.h"
diff --git a/vm/Dvm.mk b/vm/Dvm.mk
index cf0d0c9..a059b41 100644
--- a/vm/Dvm.mk
+++ b/vm/Dvm.mk
@@ -138,8 +138,9 @@
alloc/DdmHeap.c \
alloc/Verify.c \
analysis/CodeVerify.c \
- analysis/DexOptimize.c \
+ analysis/DexPrepare.c \
analysis/DexVerify.c \
+ analysis/Optimize.c \
analysis/ReduceConstants.c \
analysis/RegisterMap.c \
analysis/VerifySubs.c \
diff --git a/vm/Globals.h b/vm/Globals.h
index 07bf522..551ca1f 100644
--- a/vm/Globals.h
+++ b/vm/Globals.h
@@ -425,9 +425,6 @@
ReferenceTable jniPinRefTable;
pthread_mutex_t jniPinRefLock;
- /* special ReferenceQueue for JNI weak globals */
- Object* jniWeakGlobalRefQueue;
-
/*
* Native shared library table.
*/
diff --git a/vm/Jni.c b/vm/Jni.c
index 6692f3b..15b0294 100644
--- a/vm/Jni.c
+++ b/vm/Jni.c
@@ -918,53 +918,6 @@
dvmUnlockMutex(&gDvm.jniGlobalRefLock);
}
-
-/*
- * Get the "magic" JNI weak global ReferenceQueue. It's allocated on
- * first use.
- *
- * Returns NULL with an exception raised if allocation fails.
- */
-static Object* getWeakGlobalRefQueue(void)
-{
- /* use an indirect variable to avoid "type-punned pointer" complaints */
- Object** pGlobalQ = &gDvm.jniWeakGlobalRefQueue;
-
- if (*pGlobalQ != NULL)
- return *pGlobalQ;
-
- ClassObject* clazz = dvmFindSystemClass("Ljava/lang/ref/ReferenceQueue;");
- if (clazz == NULL) {
- LOGE("Unable to find java.lang.ref.ReferenceQueue");
- dvmAbort();
- }
-
- /*
- * Create an instance of ReferenceQueue. The object is never actually
- * used for anything, so we don't need to call a constructor. (We could
- * get away with using an instance of Object, but this is cleaner.)
- */
- Object* queue = dvmAllocObject(clazz, ALLOC_DEFAULT);
- if (queue == NULL) {
- LOGW("Failed allocating weak global ref queue\n");
- assert(dvmCheckException(dvmThreadSelf()));
- return NULL;
- }
- dvmReleaseTrackedAlloc(queue, NULL);
-
- /*
- * Save it, using atomic ops to ensure we don't double-up. The gDvm
- * field is known to the GC.
- */
- if (!ATOMIC_CMP_SWAP((int*) pGlobalQ, 0, (int) queue)) {
- LOGD("WOW: lost race to create weak global ref queue\n");
- queue = *pGlobalQ;
- }
-
- return queue;
-}
-
-
/*
* We create a PhantomReference that references the object, add a
* global reference to it, and then flip some bits before returning it.
@@ -980,7 +933,6 @@
Thread* self = ((JNIEnvExt*)env)->self;
Object* obj = dvmDecodeIndirectRef(env, jobj);
- Object* weakGlobalQueue = getWeakGlobalRefQueue();
Object* phantomObj;
jobject phantomRef;
@@ -1004,7 +956,7 @@
JValue unused;
dvmCallMethod(self, gDvm.methJavaLangRefPhantomReference_init, phantomObj,
- &unused, jobj, weakGlobalQueue);
+ &unused, jobj, NULL);
dvmReleaseTrackedAlloc(phantomObj, self);
if (dvmCheckException(self)) {
diff --git a/vm/Sync.c b/vm/Sync.c
index c00a630..3b13159 100644
--- a/vm/Sync.c
+++ b/vm/Sync.c
@@ -310,7 +310,8 @@
* the object, in which case we've got some bad
* native code somewhere.
*/
- assert(dvmTryLockMutex(&mon->lock) == 0);
+ assert(pthread_mutex_trylock(&mon->lock) == 0);
+ assert(pthread_mutex_unlock(&mon->lock) == 0);
dvmDestroyMutex(&mon->lock);
#ifdef WITH_DEADLOCK_PREDICTION
expandObjClear(&mon->historyChildren);
@@ -399,8 +400,7 @@
static bool unlockMonitor(Thread* self, Monitor* mon)
{
assert(self != NULL);
- assert(mon != NULL); // can this happen?
-
+ assert(mon != NULL);
if (mon->owner == self) {
/*
* We own the monitor, so nobody else can be in here.
@@ -418,9 +418,7 @@
* in this case.
*/
dvmThrowExceptionFmt("Ljava/lang/IllegalMonitorStateException;",
- "unlock of unowned monitor, self=%d owner=%d",
- self->threadId,
- mon->owner ? mon->owner->threadId : 0);
+ "unlock of unowned monitor");
return false;
}
return true;
diff --git a/vm/Thread.c b/vm/Thread.c
index 63b07cb..180ccf7 100644
--- a/vm/Thread.c
+++ b/vm/Thread.c
@@ -547,7 +547,9 @@
do {
cc = dvmTryLockMutex(&gDvm._threadSuspendLock);
if (cc != 0) {
- if (!dvmCheckSuspendPending(NULL)) {
+ Thread* self = dvmThreadSelf();
+
+ if (!dvmCheckSuspendPending(self)) {
/*
* Could be that a resume-all is in progress, and something
* grabbed the CPU when the wakeup was broadcast. The thread
@@ -569,7 +571,7 @@
*/
LOGI("threadid=%d ODD: want thread-suspend lock (%s:%s),"
" it's held, no suspend pending\n",
- dvmThreadSelf()->threadId, who, getSuspendCauseStr(why));
+ self->threadId, who, getSuspendCauseStr(why));
} else {
/* we suspended; reset timeout */
sleepIter = 0;
@@ -581,7 +583,7 @@
if (!dvmIterativeSleep(sleepIter++, kSpinSleepTime, startWhen)) {
LOGE("threadid=%d: couldn't get thread-suspend lock (%s:%s),"
" bailing\n",
- dvmThreadSelf()->threadId, who, getSuspendCauseStr(why));
+ self->threadId, who, getSuspendCauseStr(why));
/* threads are not suspended, thread dump could crash */
dvmDumpAllThreads(false);
dvmAbort();
@@ -3007,26 +3009,25 @@
* Check to see if we need to suspend ourselves. If so, go to sleep on
* a condition variable.
*
- * Takes "self" as an argument as an optimization. Pass in NULL to have
- * it do the lookup.
+ * If "newStatus" is not THREAD_UNDEFINED, we change to that state before
+ * we release the thread suspend count lock.
*
* Returns "true" if we suspended ourselves.
*/
-bool dvmCheckSuspendPending(Thread* self)
+static bool checkSuspendAndChangeStatus(Thread* self, ThreadStatus newStatus)
{
bool didSuspend;
- if (self == NULL)
- self = dvmThreadSelf();
+ assert(self != NULL);
+ assert(self->suspendCount >= 0);
- /* fast path: if count is zero, bail immediately */
- if (self->suspendCount == 0)
+ /* fast path: if count is zero and no state change, bail immediately */
+ if (self->suspendCount == 0 && newStatus == THREAD_UNDEFINED) {
return false;
+ }
lockThreadSuspendCount(); /* grab gDvm.threadSuspendCountLock */
- assert(self->suspendCount >= 0); /* XXX: valid? useful? */
-
didSuspend = (self->suspendCount != 0);
self->isSuspended = true;
LOG_THREAD("threadid=%d: self-suspending\n", self->threadId);
@@ -3042,12 +3043,31 @@
LOG_THREAD("threadid=%d: self-reviving, status=%d\n",
self->threadId, self->status);
+ /*
+ * The status change needs to happen while the suspend count lock is
+ * held. Otherwise we could switch to RUNNING after another thread
+ * increases our suspend count, which isn't a "bad" state for us
+ * (we'll suspend on the next check) but could be a problem for the
+ * other thread (which thinks we're safely in VMWAIT or NATIVE with
+ * a nonzero suspend count, and proceeds to initate GC).
+ */
+ if (newStatus != THREAD_UNDEFINED)
+ self->status = newStatus;
+
unlockThreadSuspendCount();
return didSuspend;
}
/*
+ * One-argument wrapper for checkSuspendAndChangeStatus().
+ */
+bool dvmCheckSuspendPending(Thread* self)
+{
+ return checkSuspendAndChangeStatus(self, THREAD_UNDEFINED);
+}
+
+/*
* Update our status.
*
* The "self" argument, which may be NULL, is accepted as an optimization.
@@ -3070,22 +3090,21 @@
/*
* Change our status to THREAD_RUNNING. The transition requires
* that we check for pending suspension, because the VM considers
- * us to be "asleep" in all other states.
+ * us to be "asleep" in all other states, and another thread could
+ * be performing a GC now.
*
- * We need to do the "suspend pending" check FIRST, because it grabs
- * a lock that could be held by something that wants us to suspend.
- * If we're in RUNNING it will wait for us, and we'll be waiting
- * for the lock it holds.
+ * The check for suspension requires holding the thread suspend
+ * count lock, which the suspend-all code also grabs. We want to
+ * check our suspension status and change to RUNNING atomically
+ * to avoid a situation where suspend-all thinks we're safe
+ * (e.g. VMWAIT or NATIVE with suspendCount=1) but we've actually
+ * switched to RUNNING and are executing code.
*/
assert(self->status != THREAD_RUNNING);
-
- dvmCheckSuspendPending(self);
- self->status = THREAD_RUNNING;
+ checkSuspendAndChangeStatus(self, newStatus);
} else {
/*
- * Change from one state to another, neither of which is
- * THREAD_RUNNING. This is most common during system or thread
- * initialization.
+ * Not changing to THREAD_RUNNING. No additional work required.
*/
self->status = newStatus;
}
diff --git a/vm/Thread.h b/vm/Thread.h
index 29cbec6..4956009 100644
--- a/vm/Thread.h
+++ b/vm/Thread.h
@@ -44,6 +44,8 @@
* Note that "suspended" is orthogonal to these values (so says JDWP).
*/
typedef enum ThreadStatus {
+ THREAD_UNDEFINED = -1, /* threads are never in this state */
+
/* these match up with JDWP values */
THREAD_ZOMBIE = 0, /* TERMINATED */
THREAD_RUNNING = 1, /* RUNNABLE or running now */
@@ -328,8 +330,6 @@
/*
* Check to see if we should be suspended now. If so, suspend ourselves
* by sleeping on a condition variable.
- *
- * If "self" is NULL, this will use dvmThreadSelf().
*/
bool dvmCheckSuspendPending(Thread* self);
diff --git a/vm/alloc/Heap.c b/vm/alloc/Heap.c
index ed4e4f3..f4d8e7b 100644
--- a/vm/alloc/Heap.c
+++ b/vm/alloc/Heap.c
@@ -67,8 +67,6 @@
gcHeap->heapWorkerCurrentObject = NULL;
gcHeap->heapWorkerCurrentMethod = NULL;
gcHeap->heapWorkerInterpStartTime = 0LL;
- gcHeap->softReferenceCollectionState = SR_COLLECT_NONE;
- gcHeap->softReferenceHeapSizeThreshold = gDvm.heapSizeStart;
gcHeap->ddmHpifWhen = 0;
gcHeap->ddmHpsgWhen = 0;
gcHeap->ddmHpsgWhat = 0;
@@ -260,50 +258,13 @@
return obj;
}
-/* Used for a heap size change hysteresis to avoid collecting
- * SoftReferences when the heap only grows by a small amount.
- */
-#define SOFT_REFERENCE_GROWTH_SLACK (128 * 1024)
-
/* Whenever the effective heap size may have changed,
* this function must be called.
*/
void dvmHeapSizeChanged()
{
- GcHeap *gcHeap = gDvm.gcHeap;
- size_t currentHeapSize;
-
- currentHeapSize = dvmHeapSourceGetIdealFootprint();
-
- /* See if the heap size has changed enough that we should care
- * about it.
- */
- if (currentHeapSize <= gcHeap->softReferenceHeapSizeThreshold -
- 4 * SOFT_REFERENCE_GROWTH_SLACK)
- {
- /* The heap has shrunk enough that we'll use this as a new
- * threshold. Since we're doing better on space, there's
- * no need to collect any SoftReferences.
- *
- * This is 4x the growth hysteresis because we don't want
- * to snap down so easily after a shrink. If we just cleared
- * up a bunch of SoftReferences, we don't want to disallow
- * any new ones from being created.
- * TODO: determine if the 4x is important, needed, or even good
- */
- gcHeap->softReferenceHeapSizeThreshold = currentHeapSize;
- gcHeap->softReferenceCollectionState = SR_COLLECT_NONE;
- } else if (currentHeapSize >= gcHeap->softReferenceHeapSizeThreshold +
- SOFT_REFERENCE_GROWTH_SLACK)
- {
- /* The heap has grown enough to warrant collecting SoftReferences.
- */
- gcHeap->softReferenceHeapSizeThreshold = currentHeapSize;
- gcHeap->softReferenceCollectionState = SR_COLLECT_SOME;
- }
}
-
/* Do a full garbage collection, which may grow the
* heap as a side-effect if the live set is large.
*/
@@ -737,13 +698,9 @@
* way to enforce this is to refuse to GC on an allocation made by the
* JDWP thread -- we have to expand the heap or fail.
*/
-void dvmCollectGarbageInternal(bool collectSoftReferences, GcReason reason)
+void dvmCollectGarbageInternal(bool clearSoftRefs, GcReason reason)
{
GcHeap *gcHeap = gDvm.gcHeap;
- Object *softReferences;
- Object *weakReferences;
- Object *phantomReferences;
-
u8 now;
s8 timeSinceLastGc;
s8 gcElapsedTime;
@@ -760,8 +717,6 @@
size_t strongMarkSize = 0;
size_t finalizeMarkCount = 0;
size_t finalizeMarkSize = 0;
- size_t phantomMarkCount = 0;
- size_t phantomMarkSize = 0;
#endif
/* The heap lock must be held.
@@ -914,18 +869,6 @@
gcHeap->weakReferences = NULL;
gcHeap->phantomReferences = NULL;
- /* Make sure that we don't hard-mark the referents of Reference
- * objects by default.
- */
- gcHeap->markAllReferents = false;
-
- /* Don't mark SoftReferences if our caller wants us to collect them.
- * This has to be set before calling dvmHeapScanMarkedObjects().
- */
- if (collectSoftReferences) {
- gcHeap->softReferenceCollectionState = SR_COLLECT_ALL;
- }
-
/* Recursively mark any objects that marked objects point to strongly.
* If we're not collecting soft references, soft-reachable
* objects will also be marked.
@@ -939,29 +882,16 @@
gcHeap->markSize = 0;
#endif
- /* Latch these so that the other calls to dvmHeapScanMarkedObjects() don't
- * mess with them.
- */
- softReferences = gcHeap->softReferences;
- weakReferences = gcHeap->weakReferences;
- phantomReferences = gcHeap->phantomReferences;
-
/* All strongly-reachable objects have now been marked.
*/
- if (gcHeap->softReferenceCollectionState != SR_COLLECT_NONE) {
- LOGD_HEAP("Handling soft references...");
- dvmHeapHandleReferences(softReferences, REF_SOFT);
- // markCount always zero
+ LOGD_HEAP("Handling soft references...");
+ if (!clearSoftRefs) {
+ dvmHandleSoftRefs(&gcHeap->softReferences);
+ }
+ dvmClearWhiteRefs(&gcHeap->softReferences);
- /* Now that we've tried collecting SoftReferences,
- * fall back to not collecting them. If the heap
- * grows, we will start collecting again.
- */
- gcHeap->softReferenceCollectionState = SR_COLLECT_NONE;
- } // else dvmHeapScanMarkedObjects() already marked the soft-reachable set
LOGD_HEAP("Handling weak references...");
- dvmHeapHandleReferences(weakReferences, REF_WEAK);
- // markCount always zero
+ dvmClearWhiteRefs(&gcHeap->weakReferences);
/* Once all weak-reachable objects have been taken
* care of, any remaining unmarked objects can be finalized.
@@ -975,24 +905,22 @@
gcHeap->markSize = 0;
#endif
+ LOGD_HEAP("Handling f-reachable soft references...");
+ dvmClearWhiteRefs(&gcHeap->softReferences);
+
+ LOGD_HEAP("Handling f-reachable weak references...");
+ dvmClearWhiteRefs(&gcHeap->weakReferences);
+
/* Any remaining objects that are not pending finalization
* could be phantom-reachable. This will mark any phantom-reachable
* objects, as well as enqueue their references.
*/
LOGD_HEAP("Handling phantom references...");
- dvmHeapHandleReferences(phantomReferences, REF_PHANTOM);
-#if DVM_TRACK_HEAP_MARKING
- phantomMarkCount = gcHeap->markCount;
- phantomMarkSize = gcHeap->markSize;
- gcHeap->markCount = 0;
- gcHeap->markSize = 0;
-#endif
-
-//TODO: take care of JNI weak global references
+ dvmClearWhiteRefs(&gcHeap->phantomReferences);
#if DVM_TRACK_HEAP_MARKING
- LOGI_HEAP("Marked objects: %dB strong, %dB final, %dB phantom\n",
- strongMarkSize, finalizeMarkSize, phantomMarkSize);
+ LOGI_HEAP("Marked objects: %dB strong, %dB final\n",
+ strongMarkSize, finalizeMarkSize);
#endif
#ifdef WITH_DEADLOCK_PREDICTION
diff --git a/vm/alloc/Heap.h b/vm/alloc/Heap.h
index 5237a56..0b0bc47 100644
--- a/vm/alloc/Heap.h
+++ b/vm/alloc/Heap.h
@@ -79,6 +79,6 @@
/*
* Run the garbage collector without doing any locking.
*/
-void dvmCollectGarbageInternal(bool collectSoftReferences, GcReason reason);
+void dvmCollectGarbageInternal(bool clearSoftRefs, GcReason reason);
#endif // _DALVIK_ALLOC_HEAP
diff --git a/vm/alloc/HeapInternal.h b/vm/alloc/HeapInternal.h
index 91d5976..f078fdd 100644
--- a/vm/alloc/HeapInternal.h
+++ b/vm/alloc/HeapInternal.h
@@ -24,8 +24,6 @@
#include "HeapTable.h"
#include "MarkSweep.h"
-#define SCHEDULED_REFERENCE_MAGIC ((Object*)0x87654321)
-
struct GcHeap {
HeapSource *heapSource;
@@ -111,31 +109,6 @@
*/
bool gcRunning;
- /* Set at the end of a GC to indicate the collection policy
- * for SoftReferences during the following GC.
- */
- enum { SR_COLLECT_NONE, SR_COLLECT_SOME, SR_COLLECT_ALL }
- softReferenceCollectionState;
-
- /* The size of the heap is compared against this value
- * to determine when to start collecting SoftReferences.
- */
- size_t softReferenceHeapSizeThreshold;
-
- /* A value that will increment every time we see a SoftReference
- * whose referent isn't marked (during SR_COLLECT_SOME).
- * The absolute value is meaningless, and does not need to
- * be reset or initialized at any point.
- */
- int softReferenceColor;
-
- /* Indicates whether or not the object scanner should bother
- * keeping track of any references. If markAllReferents is
- * true, referents will be hard-marked. If false, normal
- * reference following is used.
- */
- bool markAllReferents;
-
#if DVM_TRACK_HEAP_MARKING
/* Every time an unmarked object becomes marked, markCount
* is incremented and markSize increases by the size of
diff --git a/vm/alloc/HeapSource.c b/vm/alloc/HeapSource.c
index defedc6..98a9ba3 100644
--- a/vm/alloc/HeapSource.c
+++ b/vm/alloc/HeapSource.c
@@ -184,9 +184,9 @@
size_t heapLength;
/*
- * The object (live, allocated) bitmap.
+ * The live object bitmap.
*/
- HeapBitmap objBits;
+ HeapBitmap liveBits;
/*
* The mark bitmap.
@@ -276,7 +276,7 @@
if (isObj) {
heap->objectsAllocated++;
hs = gDvm.gcHeap->heapSource;
- dvmHeapBitmapSetObjectBit(&hs->objBits, ptr);
+ dvmHeapBitmapSetObjectBit(&hs->liveBits, ptr);
}
assert(heap->bytesAllocated < mspace_footprint(heap->msp));
@@ -297,7 +297,7 @@
}
if (isObj) {
hs = gDvm.gcHeap->heapSource;
- dvmHeapBitmapClearObjectBit(&hs->objBits, ptr);
+ dvmHeapBitmapClearObjectBit(&hs->liveBits, ptr);
if (heap->objectsAllocated > 0) {
heap->objectsAllocated--;
}
@@ -478,13 +478,13 @@
LOGE_HEAP("Can't add initial heap\n");
goto fail;
}
- if (!dvmHeapBitmapInit(&hs->objBits, base, length, "dalvik-bitmap-1")) {
- LOGE_HEAP("Can't create objBits\n");
+ if (!dvmHeapBitmapInit(&hs->liveBits, base, length, "dalvik-bitmap-1")) {
+ LOGE_HEAP("Can't create liveBits\n");
goto fail;
}
if (!dvmHeapBitmapInit(&hs->markBits, base, length, "dalvik-bitmap-2")) {
LOGE_HEAP("Can't create markBits\n");
- dvmHeapBitmapDelete(&hs->objBits);
+ dvmHeapBitmapDelete(&hs->liveBits);
goto fail;
}
@@ -546,7 +546,7 @@
assert((char *)*gcHeap >= hs->heapBase);
assert((char *)*gcHeap < hs->heapBase + hs->heapLength);
- dvmHeapBitmapDelete(&hs->objBits);
+ dvmHeapBitmapDelete(&hs->liveBits);
dvmHeapBitmapDelete(&hs->markBits);
munmap(hs->heapBase, hs->heapLength);
@@ -630,7 +630,7 @@
* object and mark bitmaps. This routine is used by the sweep code
* which needs to free each object in the correct heap.
*/
-void dvmHeapSourceGetObjectBitmaps(HeapBitmap objBits[], HeapBitmap markBits[],
+void dvmHeapSourceGetObjectBitmaps(HeapBitmap liveBits[], HeapBitmap markBits[],
size_t numHeaps)
{
HeapSource *hs = gHs;
@@ -643,7 +643,7 @@
for (i = 0; i < hs->numHeaps; ++i) {
base = (uintptr_t)hs->heaps[i].base;
max = (uintptr_t)hs->heaps[i].limit - 1;
- aliasBitmap(&objBits[i], &hs->objBits, base, max);
+ aliasBitmap(&liveBits[i], &hs->liveBits, base, max);
aliasBitmap(&markBits[i], &hs->markBits, base, max);
}
}
@@ -655,14 +655,14 @@
{
HS_BOILERPLATE();
- return &gHs->objBits;
+ return &gHs->liveBits;
}
void dvmHeapSourceSwapBitmaps(void)
{
HeapBitmap tmp;
- tmp = gHs->objBits;
- gHs->objBits = gHs->markBits;
+ tmp = gHs->liveBits;
+ gHs->liveBits = gHs->markBits;
gHs->markBits = tmp;
dvmHeapBitmapZero(&gHs->markBits);
}
@@ -676,13 +676,13 @@
* Copy the contents of the live bit vector for immune object
* range into the mark bit vector.
*/
- assert(gHs->objBits.base == gHs->markBits.base);
- assert(gHs->objBits.bitsLen == gHs->markBits.bitsLen);
+ assert(gHs->liveBits.base == gHs->markBits.base);
+ assert(gHs->liveBits.bitsLen == gHs->markBits.bitsLen);
for (i = 1; i < gHs->numHeaps; ++i) {
/* Compute the number of words to copy in the bitmap. */
- index = HB_OFFSET_TO_INDEX((uintptr_t)gHs->heaps[i].base - gHs->objBits.base);
+ index = HB_OFFSET_TO_INDEX((uintptr_t)gHs->heaps[i].base - gHs->liveBits.base);
/* Compute the starting offset in the live and mark bits. */
- src = (char *)(gHs->objBits.bits + index);
+ src = (char *)(gHs->liveBits.bits + index);
dst = (char *)(gHs->markBits.bits + index);
/* Compute the number of bytes of the live bitmap to copy. */
length = HB_OFFSET_TO_BYTE_INDEX(gHs->heaps[i].limit - gHs->heaps[i].base);
@@ -919,8 +919,8 @@
{
HS_BOILERPLATE();
- if (dvmHeapBitmapCoversAddress(&gHs->objBits, ptr)) {
- return dvmHeapBitmapIsObjectBitSet(&gHs->objBits, ptr) != 0;
+ if (dvmHeapBitmapCoversAddress(&gHs->liveBits, ptr)) {
+ return dvmHeapBitmapIsObjectBitSet(&gHs->liveBits, ptr) != 0;
}
return false;
}
diff --git a/vm/alloc/HeapTable.c b/vm/alloc/HeapTable.c
index 61a88ec..7d42600 100644
--- a/vm/alloc/HeapTable.c
+++ b/vm/alloc/HeapTable.c
@@ -202,10 +202,8 @@
lastRef = table->refs.nextEntry;
if (stripLowBits) {
/* This case is used for marking reference objects that
- * are still waiting for the heap worker thread to get to
- * them. The referents pointed to by the references are
- * marked when a SCHEDULED_REFERENCE_MAGIC is encountered
- * during scanning.
+ * are still waiting for the heap worker thread to push
+ * them onto their reference queue.
*/
while (ref < lastRef) {
dvmMarkObjectNonNull((Object *)((uintptr_t)*ref++ & ~3));
diff --git a/vm/alloc/HeapWorker.c b/vm/alloc/HeapWorker.c
index c769521..bf924cd 100644
--- a/vm/alloc/HeapWorker.c
+++ b/vm/alloc/HeapWorker.c
@@ -278,6 +278,10 @@
callMethod(self, obj, method);
} else {
if (op & WORKER_ENQUEUE) {
+ assert(dvmGetFieldObject(
+ obj, gDvm.offJavaLangRefReference_queue) != NULL);
+ assert(dvmGetFieldObject(
+ obj, gDvm.offJavaLangRefReference_queueNext) == NULL);
numReferencesEnqueued++;
callMethod(self, obj,
gDvm.methJavaLangRefReference_enqueueInternal);
diff --git a/vm/alloc/MarkSweep.c b/vm/alloc/MarkSweep.c
index 1b62cf4..d9860d9 100644
--- a/vm/alloc/MarkSweep.c
+++ b/vm/alloc/MarkSweep.c
@@ -329,7 +329,6 @@
dvmMarkObjectNonNull(gDvm.outOfMemoryObj);
dvmMarkObjectNonNull(gDvm.internalErrorObj);
dvmMarkObjectNonNull(gDvm.noClassDefFoundErrorObj);
- dvmMarkObject(gDvm.jniWeakGlobalRefQueue);
//TODO: scan object references sitting in gDvm; use pointer begin & end
HPROF_CLEAR_GC_SCAN_STATE();
@@ -538,43 +537,10 @@
*/
referent = dvmGetFieldObject(obj,
gDvm.offJavaLangRefReference_referent);
- if (referent != NULL &&
- !isMarked(referent, &gcHeap->markContext))
+ if (referent != NULL && !isMarked(referent, ctx))
{
u4 refFlags;
- if (gcHeap->markAllReferents) {
- LOG_REF("Hard-marking a reference\n");
-
- /* Don't bother with normal reference-following
- * behavior, just mark the referent. This should
- * only be used when following objects that just
- * became scheduled for finalization.
- */
- markObjectNonNull(referent, ctx);
- goto skip_reference;
- }
-
- /* See if this reference was handled by a previous GC.
- */
- if (dvmGetFieldObject(obj,
- gDvm.offJavaLangRefReference_vmData) ==
- SCHEDULED_REFERENCE_MAGIC)
- {
- LOG_REF("Skipping scheduled reference\n");
-
- /* Don't reschedule it, but make sure that its
- * referent doesn't get collected (in case it's
- * a PhantomReference and wasn't cleared automatically).
- */
- //TODO: Mark these after handling all new refs of
- // this strength, in case the new refs refer
- // to the same referent. Not a very common
- // case, though.
- markObjectNonNull(referent, ctx);
- goto skip_reference;
- }
-
/* Find out what kind of reference is pointing
* to referent.
*/
@@ -606,25 +572,7 @@
* we'll attempt to collect all of them, some of
* them, or none of them.
*/
- if (gcHeap->softReferenceCollectionState ==
- SR_COLLECT_NONE)
- {
- sr_collect_none:
- markObjectNonNull(referent, ctx);
- } else if (gcHeap->softReferenceCollectionState ==
- SR_COLLECT_ALL)
- {
- sr_collect_all:
- ADD_REF_TO_LIST(gcHeap->softReferences, obj);
- } else {
- /* We'll only try to collect half of the
- * referents.
- */
- if (gcHeap->softReferenceColor++ & 1) {
- goto sr_collect_none;
- }
- goto sr_collect_all;
- }
+ ADD_REF_TO_LIST(gcHeap->softReferences, obj);
} else {
/* It's a weak or phantom reference.
* Clearing CLASS_ISREFERENCE will reveal which.
@@ -642,7 +590,6 @@
}
}
- skip_reference:
/* If this is a class object, mark various other things that
* its internals point to.
*
@@ -697,7 +644,7 @@
* reachable objects. When this returns, the entire set of
* live objects will be marked and the mark stack will be empty.
*/
-void dvmHeapScanMarkedObjects()
+void dvmHeapScanMarkedObjects(void)
{
GcMarkContext *ctx = &gDvm.gcHeap->markContext;
@@ -735,9 +682,11 @@
gDvm.offJavaLangRefReference_referent, NULL);
}
-/** @return true if we need to schedule a call to enqueue().
+/*
+ * Returns true if the reference was registered with a reference queue
+ * and has not yet been enqueued.
*/
-static bool enqueueReference(Object *reference)
+static bool isEnqueuable(const Object *reference)
{
Object *queue = dvmGetFieldObject(reference,
gDvm.offJavaLangRefReference_queue);
@@ -757,161 +706,124 @@
return true;
}
-/* All objects for stronger reference levels have been
- * marked before this is called.
+/*
+ * Schedules a reference to be appended to its reference queue.
*/
-void dvmHeapHandleReferences(Object *refListHead, enum RefType refType)
+static void enqueueReference(Object *ref)
{
- Object *reference;
- GcMarkContext *markContext = &gDvm.gcHeap->markContext;
- const int offVmData = gDvm.offJavaLangRefReference_vmData;
- const int offReferent = gDvm.offJavaLangRefReference_referent;
- bool workRequired = false;
+ LargeHeapRefTable **table;
+ Object *op;
- reference = refListHead;
- while (reference != NULL) {
- Object *next;
- Object *referent;
-
- /* Pull the interesting fields out of the Reference object.
- */
- next = dvmGetFieldObject(reference, offVmData);
- referent = dvmGetFieldObject(reference, offReferent);
-
- //TODO: when handling REF_PHANTOM, unlink any references
- // that fail this initial if(). We need to re-walk
- // the list, and it would be nice to avoid the extra
- // work.
- if (referent != NULL && !isMarked(referent, markContext)) {
- bool schedEnqueue;
-
- /* This is the strongest reference that refers to referent.
- * Do the right thing.
- */
- switch (refType) {
- case REF_SOFT:
- case REF_WEAK:
- clearReference(reference);
- schedEnqueue = enqueueReference(reference);
- break;
- case REF_PHANTOM:
- /* PhantomReferences are not cleared automatically.
- * Until someone clears it (or the reference itself
- * is collected), the referent must remain alive.
- *
- * It's necessary to fully mark the referent because
- * it will still be present during the next GC, and
- * all objects that it points to must be valid.
- * (The referent will be marked outside of this loop,
- * after handing all references of this strength, in
- * case multiple references point to the same object.)
- *
- * One exception: JNI "weak global" references are handled
- * as a special case. They're identified by the queue.
- */
- if (gDvm.jniWeakGlobalRefQueue != NULL) {
- Object* queue = dvmGetFieldObject(reference,
- gDvm.offJavaLangRefReference_queue);
- if (queue == gDvm.jniWeakGlobalRefQueue) {
- LOGV("+++ WGR: clearing + not queueing %p:%p\n",
- reference, referent);
- clearReference(reference);
- schedEnqueue = false;
- break;
- }
- }
-
- /* A PhantomReference is only useful with a
- * queue, but since it's possible to create one
- * without a queue, we need to check.
- */
- schedEnqueue = enqueueReference(reference);
- break;
- default:
- assert(!"Bad reference type");
- schedEnqueue = false;
- break;
- }
-
- if (schedEnqueue) {
- /* Stuff the enqueue bit in the bottom of the pointer.
- * Assumes that objects are 8-byte aligned.
- *
- * Note that we are adding the *Reference* (which
- * is by definition already marked at this point) to
- * this list; we're not adding the referent (which
- * has already been cleared).
- */
- assert(((intptr_t)reference & 3) == 0);
- assert((WORKER_ENQUEUE & ~3) == 0);
- if (!dvmHeapAddRefToLargeTable(
- &gDvm.gcHeap->referenceOperations,
- (Object *)((uintptr_t)reference | WORKER_ENQUEUE)))
- {
- LOGE_HEAP("dvmMalloc(): no room for any more "
- "reference operations\n");
- dvmAbort();
- }
- workRequired = true;
- }
-
- if (refType != REF_PHANTOM) {
- /* Let later GCs know not to reschedule this reference.
- */
- dvmSetFieldObject(reference, offVmData,
- SCHEDULED_REFERENCE_MAGIC);
- } // else this is handled later for REF_PHANTOM
-
- } // else there was a stronger reference to the referent.
-
- reference = next;
- }
-
- /* Walk though the reference list again, and mark any non-clear/marked
- * referents. Only PhantomReferences can have non-clear referents
- * at this point.
+ assert(((uintptr_t)ref & 3) == 0);
+ assert((WORKER_ENQUEUE & ~3) == 0);
+ assert(dvmGetFieldObject(ref, gDvm.offJavaLangRefReference_queue) != NULL);
+ assert(dvmGetFieldObject(ref, gDvm.offJavaLangRefReference_queueNext) == NULL);
+ /* Stuff the enqueue bit in the bottom of the pointer.
+ * Assumes that objects are 8-byte aligned.
*
- * (Could skip this for JNI weak globals, since we know they've been
- * cleared.)
+ * Note that we are adding the *Reference* (which
+ * is by definition already marked at this point) to
+ * this list; we're not adding the referent (which
+ * has already been cleared).
*/
- if (refType == REF_PHANTOM) {
- bool scanRequired = false;
-
- HPROF_SET_GC_SCAN_STATE(HPROF_ROOT_REFERENCE_CLEANUP, 0);
- reference = refListHead;
- while (reference != NULL) {
- Object *next;
- Object *referent;
-
- /* Pull the interesting fields out of the Reference object.
- */
- next = dvmGetFieldObject(reference, offVmData);
- referent = dvmGetFieldObject(reference, offReferent);
-
- if (referent != NULL && !isMarked(referent, markContext)) {
- markObjectNonNull(referent, markContext);
- scanRequired = true;
-
- /* Let later GCs know not to reschedule this reference.
- */
- dvmSetFieldObject(reference, offVmData,
- SCHEDULED_REFERENCE_MAGIC);
- }
-
- reference = next;
- }
- HPROF_CLEAR_GC_SCAN_STATE();
-
- if (scanRequired) {
- processMarkStack(markContext);
- }
- }
-
- if (workRequired) {
- dvmSignalHeapWorker(false);
+ table = &gDvm.gcHeap->referenceOperations;
+ op = (Object *)((uintptr_t)ref | WORKER_ENQUEUE);
+ if (!dvmHeapAddRefToLargeTable(table, op)) {
+ LOGE_HEAP("enqueueReference(): no room for any more "
+ "reference operations\n");
+ dvmAbort();
}
}
+/*
+ * Walks the reference list marking any references subject to the
+ * reference clearing policy. References with a black referent are
+ * removed from the list. References with white referents biased
+ * toward saving are blackened and also removed from the list.
+ */
+void dvmHandleSoftRefs(Object **list)
+{
+ GcMarkContext *markContext;
+ Object *ref, *referent;
+ Object *prev, *next;
+ size_t referentOffset, vmDataOffset;
+ unsigned counter;
+ bool marked;
+
+ markContext = &gDvm.gcHeap->markContext;
+ vmDataOffset = gDvm.offJavaLangRefReference_vmData;
+ referentOffset = gDvm.offJavaLangRefReference_referent;
+ counter = 0;
+ prev = next = NULL;
+ ref = *list;
+ while (ref != NULL) {
+ referent = dvmGetFieldObject(ref, referentOffset);
+ next = dvmGetFieldObject(ref, vmDataOffset);
+ assert(referent != NULL);
+ marked = isMarked(referent, markContext);
+ if (!marked && ((++counter) & 1)) {
+ /* Referent is white and biased toward saving, mark it. */
+ markObjectNonNull(referent, markContext);
+ marked = true;
+ }
+ if (marked) {
+ /* Referent is black, unlink it. */
+ if (prev != NULL) {
+ dvmSetFieldObject(ref, vmDataOffset, NULL);
+ dvmSetFieldObject(prev, vmDataOffset, next);
+ }
+ } else {
+ /* Referent is white, skip over it. */
+ prev = ref;
+ }
+ ref = next;
+ }
+ /*
+ * Restart the mark with the newly black references added to the
+ * root set.
+ */
+ processMarkStack(markContext);
+}
+
+/*
+ * Walks the reference list and clears references with an unmarked
+ * (white) referents. Cleared references registered to a reference
+ * queue are scheduled for appending by the heap worker thread.
+ */
+void dvmClearWhiteRefs(Object **list)
+{
+ GcMarkContext *markContext;
+ Object *ref, *referent;
+ size_t referentOffset, vmDataOffset;
+ bool doSignal;
+
+ markContext = &gDvm.gcHeap->markContext;
+ vmDataOffset = gDvm.offJavaLangRefReference_vmData;
+ referentOffset = gDvm.offJavaLangRefReference_referent;
+ doSignal = false;
+ while (*list != NULL) {
+ ref = *list;
+ referent = dvmGetFieldObject(ref, referentOffset);
+ *list = dvmGetFieldObject(ref, vmDataOffset);
+ assert(referent != NULL);
+ if (!isMarked(referent, markContext)) {
+ /* Referent is "white", clear it. */
+ clearReference(ref);
+ if (isEnqueuable(ref)) {
+ enqueueReference(ref);
+ doSignal = true;
+ }
+ }
+ }
+ /*
+ * If we cleared a reference with a reference queue we must notify
+ * the heap worker to append the reference.
+ */
+ if (doSignal) {
+ dvmSignalHeapWorker(false);
+ }
+ assert(*list == NULL);
+}
/* Find unreachable objects that need to be finalized,
* and schedule them for finalization.
@@ -1018,16 +930,7 @@
ref++;
}
HPROF_CLEAR_GC_SCAN_STATE();
-
- /* Set markAllReferents so that we don't collect referents whose
- * only references are in final-reachable objects.
- * TODO: eventually provide normal reference behavior by properly
- * marking these references.
- */
- gDvm.gcHeap->markAllReferents = true;
processMarkStack(markContext);
- gDvm.gcHeap->markAllReferents = false;
-
dvmSignalHeapWorker(false);
}
@@ -1119,7 +1022,7 @@
dvmHeapSweepUnmarkedObjects(GcMode mode, int *numFreed, size_t *sizeFreed)
{
HeapBitmap markBits[HEAP_SOURCE_MAX_HEAP_COUNT];
- HeapBitmap objBits[HEAP_SOURCE_MAX_HEAP_COUNT];
+ HeapBitmap liveBits[HEAP_SOURCE_MAX_HEAP_COUNT];
size_t origObjectsAllocated;
size_t origBytesAllocated;
size_t numBitmaps, numSweepBitmaps;
@@ -1138,14 +1041,14 @@
dvmSweepMonitorList(&gDvm.monitorList, isUnmarkedObject);
numBitmaps = dvmHeapSourceGetNumHeaps();
- dvmHeapSourceGetObjectBitmaps(objBits, markBits, numBitmaps);
+ dvmHeapSourceGetObjectBitmaps(liveBits, markBits, numBitmaps);
if (mode == GC_PARTIAL) {
numSweepBitmaps = 1;
- assert((uintptr_t)gDvm.gcHeap->markContext.immuneLimit == objBits[0].base);
+ assert((uintptr_t)gDvm.gcHeap->markContext.immuneLimit == liveBits[0].base);
} else {
numSweepBitmaps = numBitmaps;
}
- dvmHeapBitmapXorWalkLists(markBits, objBits, numSweepBitmaps,
+ dvmHeapBitmapXorWalkLists(markBits, liveBits, numSweepBitmaps,
sweepBitmapCallback, NULL);
*numFreed = origObjectsAllocated -
diff --git a/vm/alloc/MarkSweep.h b/vm/alloc/MarkSweep.h
index e6d55bc..5323628 100644
--- a/vm/alloc/MarkSweep.h
+++ b/vm/alloc/MarkSweep.h
@@ -54,7 +54,8 @@
bool dvmHeapBeginMarkStep(GcMode mode);
void dvmHeapMarkRootSet(void);
void dvmHeapScanMarkedObjects(void);
-void dvmHeapHandleReferences(Object *refListHead, enum RefType refType);
+void dvmHandleSoftRefs(Object **list);
+void dvmClearWhiteRefs(Object **list);
void dvmHeapScheduleFinalizations(void);
void dvmHeapFinishMarkStep(void);
diff --git a/vm/alloc/Verify.c b/vm/alloc/Verify.c
index 20e7b8a..db552e8 100644
--- a/vm/alloc/Verify.c
+++ b/vm/alloc/Verify.c
@@ -19,7 +19,9 @@
#include "alloc/Verify.h"
#include "alloc/HeapBitmap.h"
-/* comment everything, of course! */
+/*
+ * Assertion that the given reference points to a valid object.
+ */
#define VERIFY_REFERENCE(x) do { \
if (!verifyReference((x), &(x))) { \
LOGE("Verify of %p at %p failed", (x), &(x)); \
@@ -123,12 +125,9 @@
}
}
if (IS_CLASS_FLAG_SET(obj->obj.clazz, CLASS_ISREFERENCE)) {
- /*
- * Reference.referent is not included in the above loop. See
- * precacheReferenceOffsets in Class.c for details.
- */
- addr = BYTE_OFFSET((Object *)obj,
- gDvm.offJavaLangRefReference_referent);
+ /* Verify the hidden Reference.referent field. */
+ offset = gDvm.offJavaLangRefReference_referent;
+ addr = BYTE_OFFSET((Object *)obj, offset);
VERIFY_REFERENCE(((JValue *)addr)->l);
}
LOGV("Exiting verifyDataObject(obj=%p) %zx", obj, length);
diff --git a/vm/analysis/CodeVerify.c b/vm/analysis/CodeVerify.c
index 8187354..91f5414 100644
--- a/vm/analysis/CodeVerify.c
+++ b/vm/analysis/CodeVerify.c
@@ -29,6 +29,7 @@
*/
#include "Dalvik.h"
#include "analysis/CodeVerify.h"
+#include "analysis/Optimize.h"
#include "analysis/RegisterMap.h"
#include "libdex/DexCatch.h"
#include "libdex/InstrUtils.h"
@@ -5561,7 +5562,8 @@
/*
* If we didn't just set the result register, clear it out. This
* ensures that you can only use "move-result" immediately after the
- * result is set.
+ * result is set. (We could check this statically, but it's not
+ * expensive and it makes our debugging output cleaner.)
*/
if (!justSetResult) {
int reg = RESULT_REGISTER(insnRegCount);
diff --git a/vm/analysis/DexOptimize.c b/vm/analysis/DexOptimize.c
deleted file mode 100644
index ae1edfc..0000000
--- a/vm/analysis/DexOptimize.c
+++ /dev/null
@@ -1,2383 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/*
- * Convert the output from "dx" into a locally-optimized DEX file.
- *
- * TODO: the format of the optimized header is currently "whatever we
- * happen to write", since the VM that writes it is by definition the same
- * as the VM that reads it. Still, it should be better documented and
- * more rigorously structured.
- */
-#include "Dalvik.h"
-#include "libdex/InstrUtils.h"
-#include "libdex/OptInvocation.h"
-#include "analysis/RegisterMap.h"
-
-#include <zlib.h>
-
-#include <stdlib.h>
-#include <unistd.h>
-#include <sys/mman.h>
-#include <sys/stat.h>
-#include <sys/file.h>
-#include <sys/wait.h>
-#include <fcntl.h>
-#include <errno.h>
-
-/*
- * Virtual/direct calls to "method" are replaced with an execute-inline
- * instruction with index "idx".
- */
-typedef struct InlineSub {
- Method* method;
- int inlineIdx;
-} InlineSub;
-
-
-/* fwd */
-static int writeDependencies(int fd, u4 modWhen, u4 crc);
-static bool writeAuxData(int fd, const DexClassLookup* pClassLookup,\
- const IndexMapSet* pIndexMapSet, const RegisterMapBuilder* pRegMapBuilder);
-static void logFailedWrite(size_t expected, ssize_t actual, const char* msg,
- int err);
-static bool computeFileChecksum(int fd, off_t start, size_t length, u4* pSum);
-
-static bool rewriteDex(u1* addr, int len, bool doVerify, bool doOpt,\
- u4* pHeaderFlags, DexClassLookup** ppClassLookup);
-static void updateChecksum(u1* addr, int len, DexHeader* pHeader);
-static bool loadAllClasses(DvmDex* pDvmDex);
-static void optimizeLoadedClasses(DexFile* pDexFile);
-static void optimizeClass(ClassObject* clazz, const InlineSub* inlineSubs);
-static bool optimizeMethod(Method* method, const InlineSub* inlineSubs);
-static void rewriteInstField(Method* method, u2* insns, OpCode newOpc);
-static bool rewriteVirtualInvoke(Method* method, u2* insns, OpCode newOpc);
-static bool rewriteEmptyDirectInvoke(Method* method, u2* insns);
-static bool rewriteExecuteInline(Method* method, u2* insns,
- MethodType methodType, const InlineSub* inlineSubs);
-static bool rewriteExecuteInlineRange(Method* method, u2* insns,
- MethodType methodType, const InlineSub* inlineSubs);
-
-
-/*
- * Return the fd of an open file in the DEX file cache area. If the cache
- * file doesn't exist or is out of date, this will remove the old entry,
- * create a new one (writing only the file header), and return with the
- * "new file" flag set.
- *
- * It's possible to execute from an unoptimized DEX file directly,
- * assuming the byte ordering and structure alignment is correct, but
- * disadvantageous because some significant optimizations are not possible.
- * It's not generally possible to do the same from an uncompressed Jar
- * file entry, because we have to guarantee 32-bit alignment in the
- * memory-mapped file.
- *
- * For a Jar/APK file (a zip archive with "classes.dex" inside), "modWhen"
- * and "crc32" come from the Zip directory entry. For a stand-alone DEX
- * file, it's the modification date of the file and the Adler32 from the
- * DEX header (which immediately follows the magic). If these don't
- * match what's stored in the opt header, we reject the file immediately.
- *
- * On success, the file descriptor will be positioned just past the "opt"
- * file header, and will be locked with flock. "*pCachedName" will point
- * to newly-allocated storage.
- */
-int dvmOpenCachedDexFile(const char* fileName, const char* cacheFileName,
- u4 modWhen, u4 crc, bool isBootstrap, bool* pNewFile, bool createIfMissing)
-{
- int fd, cc;
- struct stat fdStat, fileStat;
- bool readOnly = false;
-
- *pNewFile = false;
-
-retry:
- /*
- * Try to open the cache file. If we've been asked to,
- * create it if it doesn't exist.
- */
- fd = createIfMissing ? open(cacheFileName, O_CREAT|O_RDWR, 0644) : -1;
- if (fd < 0) {
- fd = open(cacheFileName, O_RDONLY, 0);
- if (fd < 0) {
- if (createIfMissing) {
- LOGE("Can't open dex cache '%s': %s\n",
- cacheFileName, strerror(errno));
- }
- return fd;
- }
- readOnly = true;
- }
-
- /*
- * Grab an exclusive lock on the cache file. If somebody else is
- * working on it, we'll block here until they complete. Because
- * we're waiting on an external resource, we go into VMWAIT mode.
- */
- int oldStatus;
- LOGV("DexOpt: locking cache file %s (fd=%d, boot=%d)\n",
- cacheFileName, fd, isBootstrap);
- oldStatus = dvmChangeStatus(NULL, THREAD_VMWAIT);
- cc = flock(fd, LOCK_EX | LOCK_NB);
- if (cc != 0) {
- LOGD("DexOpt: sleeping on flock(%s)\n", cacheFileName);
- cc = flock(fd, LOCK_EX);
- }
- dvmChangeStatus(NULL, oldStatus);
- if (cc != 0) {
- LOGE("Can't lock dex cache '%s': %d\n", cacheFileName, cc);
- close(fd);
- return -1;
- }
- LOGV("DexOpt: locked cache file\n");
-
- /*
- * Check to see if the fd we opened and locked matches the file in
- * the filesystem. If they don't, then somebody else unlinked ours
- * and created a new file, and we need to use that one instead. (If
- * we caught them between the unlink and the create, we'll get an
- * ENOENT from the file stat.)
- */
- cc = fstat(fd, &fdStat);
- if (cc != 0) {
- LOGE("Can't stat open file '%s'\n", cacheFileName);
- LOGVV("DexOpt: unlocking cache file %s\n", cacheFileName);
- goto close_fail;
- }
- cc = stat(cacheFileName, &fileStat);
- if (cc != 0 ||
- fdStat.st_dev != fileStat.st_dev || fdStat.st_ino != fileStat.st_ino)
- {
- LOGD("DexOpt: our open cache file is stale; sleeping and retrying\n");
- LOGVV("DexOpt: unlocking cache file %s\n", cacheFileName);
- flock(fd, LOCK_UN);
- close(fd);
- usleep(250 * 1000); /* if something is hosed, don't peg machine */
- goto retry;
- }
-
- /*
- * We have the correct file open and locked. If the file size is zero,
- * then it was just created by us, and we want to fill in some fields
- * in the "opt" header and set "*pNewFile". Otherwise, we want to
- * verify that the fields in the header match our expectations, and
- * reset the file if they don't.
- */
- if (fdStat.st_size == 0) {
- if (readOnly) {
- LOGW("DexOpt: file has zero length and isn't writable\n");
- goto close_fail;
- }
- cc = dexOptCreateEmptyHeader(fd);
- if (cc != 0)
- goto close_fail;
- *pNewFile = true;
- LOGV("DexOpt: successfully initialized new cache file\n");
- } else {
- bool expectVerify, expectOpt;
-
- if (gDvm.classVerifyMode == VERIFY_MODE_NONE)
- expectVerify = false;
- else if (gDvm.classVerifyMode == VERIFY_MODE_REMOTE)
- expectVerify = !isBootstrap;
- else /*if (gDvm.classVerifyMode == VERIFY_MODE_ALL)*/
- expectVerify = true;
-
- if (gDvm.dexOptMode == OPTIMIZE_MODE_NONE)
- expectOpt = false;
- else if (gDvm.dexOptMode == OPTIMIZE_MODE_VERIFIED)
- expectOpt = expectVerify;
- else /*if (gDvm.dexOptMode == OPTIMIZE_MODE_ALL)*/
- expectOpt = true;
-
- LOGV("checking deps, expecting vfy=%d opt=%d\n",
- expectVerify, expectOpt);
-
- if (!dvmCheckOptHeaderAndDependencies(fd, true, modWhen, crc,
- expectVerify, expectOpt))
- {
- if (readOnly) {
- /*
- * We could unlink and rewrite the file if we own it or
- * the "sticky" bit isn't set on the directory. However,
- * we're not able to truncate it, which spoils things. So,
- * give up now.
- */
- if (createIfMissing) {
- LOGW("Cached DEX '%s' (%s) is stale and not writable\n",
- fileName, cacheFileName);
- }
- goto close_fail;
- }
-
- /*
- * If we truncate the existing file before unlinking it, any
- * process that has it mapped will fail when it tries to touch
- * the pages.
- *
- * This is very important. The zygote process will have the
- * boot DEX files (core, framework, etc.) mapped early. If
- * (say) core.dex gets updated, and somebody launches an app
- * that uses App.dex, then App.dex gets reoptimized because it's
- * dependent upon the boot classes. However, dexopt will be
- * using the *new* core.dex to do the optimizations, while the
- * app will actually be running against the *old* core.dex
- * because it starts from zygote.
- *
- * Even without zygote, it's still possible for a class loader
- * to pull in an APK that was optimized against an older set
- * of DEX files. We must ensure that everything fails when a
- * boot DEX gets updated, and for general "why aren't my
- * changes doing anything" purposes its best if we just make
- * everything crash when a DEX they're using gets updated.
- */
- LOGD("Stale deps in cache file; removing and retrying\n");
- if (ftruncate(fd, 0) != 0) {
- LOGW("Warning: unable to truncate cache file '%s': %s\n",
- cacheFileName, strerror(errno));
- /* keep going */
- }
- if (unlink(cacheFileName) != 0) {
- LOGW("Warning: unable to remove cache file '%s': %d %s\n",
- cacheFileName, errno, strerror(errno));
- /* keep going; permission failure should probably be fatal */
- }
- LOGVV("DexOpt: unlocking cache file %s\n", cacheFileName);
- flock(fd, LOCK_UN);
- close(fd);
- goto retry;
- } else {
- LOGV("DexOpt: good deps in cache file\n");
- }
- }
-
- assert(fd >= 0);
- return fd;
-
-close_fail:
- flock(fd, LOCK_UN);
- close(fd);
- return -1;
-}
-
-/*
- * Unlock the file descriptor.
- *
- * Returns "true" on success.
- */
-bool dvmUnlockCachedDexFile(int fd)
-{
- LOGVV("DexOpt: unlocking cache file fd=%d\n", fd);
- return (flock(fd, LOCK_UN) == 0);
-}
-
-
-/*
- * Given a descriptor for a file with DEX data in it, produce an
- * optimized version.
- *
- * The file pointed to by "fd" is expected to be a locked shared resource
- * (or private); we make no efforts to enforce multi-process correctness
- * here.
- *
- * "fileName" is only used for debug output. "modWhen" and "crc" are stored
- * in the dependency set.
- *
- * The "isBootstrap" flag determines how the optimizer and verifier handle
- * package-scope access checks. When optimizing, we only load the bootstrap
- * class DEX files and the target DEX, so the flag determines whether the
- * target DEX classes are given a (synthetic) non-NULL classLoader pointer.
- * This only really matters if the target DEX contains classes that claim to
- * be in the same package as bootstrap classes.
- *
- * The optimizer will need to load every class in the target DEX file.
- * This is generally undesirable, so we start a subprocess to do the
- * work and wait for it to complete.
- *
- * Returns "true" on success. All data will have been written to "fd".
- */
-bool dvmOptimizeDexFile(int fd, off_t dexOffset, long dexLength,
- const char* fileName, u4 modWhen, u4 crc, bool isBootstrap)
-{
- const char* lastPart = strrchr(fileName, '/');
- if (lastPart != NULL)
- lastPart++;
- else
- lastPart = fileName;
-
- /*
- * For basic optimizations (byte-swapping and structure aligning) we
- * don't need to fork(). It looks like fork+exec is causing problems
- * with gdb on our bewildered Linux distro, so in some situations we
- * want to avoid this.
- *
- * For optimization and/or verification, we need to load all the classes.
- *
- * We don't check gDvm.generateRegisterMaps, since that is dependent
- * upon the verifier state.
- */
- if (gDvm.classVerifyMode == VERIFY_MODE_NONE &&
- (gDvm.dexOptMode == OPTIMIZE_MODE_NONE ||
- gDvm.dexOptMode == OPTIMIZE_MODE_VERIFIED))
- {
- LOGD("DexOpt: --- BEGIN (quick) '%s' ---\n", lastPart);
- return dvmContinueOptimization(fd, dexOffset, dexLength,
- fileName, modWhen, crc, isBootstrap);
- }
-
-
- LOGD("DexOpt: --- BEGIN '%s' (bootstrap=%d) ---\n", lastPart, isBootstrap);
-
- pid_t pid;
-
- /*
- * This could happen if something in our bootclasspath, which we thought
- * was all optimized, got rejected.
- */
- if (gDvm.optimizing) {
- LOGW("Rejecting recursive optimization attempt on '%s'\n", fileName);
- return false;
- }
-
- pid = fork();
- if (pid == 0) {
- static const int kUseValgrind = 0;
- static const char* kDexOptBin = "/bin/dexopt";
- static const char* kValgrinder = "/usr/bin/valgrind";
- static const int kFixedArgCount = 10;
- static const int kValgrindArgCount = 5;
- static const int kMaxIntLen = 12; // '-'+10dig+'\0' -OR- 0x+8dig
- int bcpSize = dvmGetBootPathSize();
- int argc = kFixedArgCount + bcpSize
- + (kValgrindArgCount * kUseValgrind);
- char* argv[argc+1]; // last entry is NULL
- char values[argc][kMaxIntLen];
- char* execFile;
- char* androidRoot;
- int flags;
-
- /* change process groups, so we don't clash with ProcessManager */
- setpgid(0, 0);
-
- /* full path to optimizer */
- androidRoot = getenv("ANDROID_ROOT");
- if (androidRoot == NULL) {
- LOGW("ANDROID_ROOT not set, defaulting to /system\n");
- androidRoot = "/system";
- }
- execFile = malloc(strlen(androidRoot) + strlen(kDexOptBin) + 1);
- strcpy(execFile, androidRoot);
- strcat(execFile, kDexOptBin);
-
- /*
- * Create arg vector.
- */
- int curArg = 0;
-
- if (kUseValgrind) {
- /* probably shouldn't ship the hard-coded path */
- argv[curArg++] = (char*)kValgrinder;
- argv[curArg++] = "--tool=memcheck";
- argv[curArg++] = "--leak-check=yes"; // check for leaks too
- argv[curArg++] = "--leak-resolution=med"; // increase from 2 to 4
- argv[curArg++] = "--num-callers=16"; // default is 12
- assert(curArg == kValgrindArgCount);
- }
- argv[curArg++] = execFile;
-
- argv[curArg++] = "--dex";
-
- sprintf(values[2], "%d", DALVIK_VM_BUILD);
- argv[curArg++] = values[2];
-
- sprintf(values[3], "%d", fd);
- argv[curArg++] = values[3];
-
- sprintf(values[4], "%d", (int) dexOffset);
- argv[curArg++] = values[4];
-
- sprintf(values[5], "%d", (int) dexLength);
- argv[curArg++] = values[5];
-
- argv[curArg++] = (char*)fileName;
-
- sprintf(values[7], "%d", (int) modWhen);
- argv[curArg++] = values[7];
-
- sprintf(values[8], "%d", (int) crc);
- argv[curArg++] = values[8];
-
- flags = 0;
- if (gDvm.dexOptMode != OPTIMIZE_MODE_NONE) {
- flags |= DEXOPT_OPT_ENABLED;
- if (gDvm.dexOptMode == OPTIMIZE_MODE_ALL)
- flags |= DEXOPT_OPT_ALL;
- }
- if (gDvm.classVerifyMode != VERIFY_MODE_NONE) {
- flags |= DEXOPT_VERIFY_ENABLED;
- if (gDvm.classVerifyMode == VERIFY_MODE_ALL)
- flags |= DEXOPT_VERIFY_ALL;
- }
- if (isBootstrap)
- flags |= DEXOPT_IS_BOOTSTRAP;
- if (gDvm.generateRegisterMaps)
- flags |= DEXOPT_GEN_REGISTER_MAP;
- sprintf(values[9], "%d", flags);
- argv[curArg++] = values[9];
-
- assert(((!kUseValgrind && curArg == kFixedArgCount) ||
- ((kUseValgrind && curArg == kFixedArgCount+kValgrindArgCount))));
-
- ClassPathEntry* cpe;
- for (cpe = gDvm.bootClassPath; cpe->ptr != NULL; cpe++) {
- argv[curArg++] = cpe->fileName;
- }
- assert(curArg == argc);
-
- argv[curArg] = NULL;
-
- if (kUseValgrind)
- execv(kValgrinder, argv);
- else
- execv(execFile, argv);
-
- LOGE("execv '%s'%s failed: %s\n", execFile,
- kUseValgrind ? " [valgrind]" : "", strerror(errno));
- exit(1);
- } else {
- LOGV("DexOpt: waiting for verify+opt, pid=%d\n", (int) pid);
- int status;
- pid_t gotPid;
- int oldStatus;
-
- /*
- * Wait for the optimization process to finish. We go into VMWAIT
- * mode here so GC suspension won't have to wait for us.
- */
- oldStatus = dvmChangeStatus(NULL, THREAD_VMWAIT);
- while (true) {
- gotPid = waitpid(pid, &status, 0);
- if (gotPid == -1 && errno == EINTR) {
- LOGD("waitpid interrupted, retrying\n");
- } else {
- break;
- }
- }
- dvmChangeStatus(NULL, oldStatus);
- if (gotPid != pid) {
- LOGE("waitpid failed: wanted %d, got %d: %s\n",
- (int) pid, (int) gotPid, strerror(errno));
- return false;
- }
-
- if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
- LOGD("DexOpt: --- END '%s' (success) ---\n", lastPart);
- return true;
- } else {
- LOGW("DexOpt: --- END '%s' --- status=0x%04x, process failed\n",
- lastPart, status);
- return false;
- }
- }
-}
-
-/*
- * Do the actual optimization. This is called directly for "minimal"
- * optimization, or from a newly-created process for "full" optimization.
- *
- * For best use of disk/memory, we want to extract once and perform
- * optimizations in place. If the file has to expand or contract
- * to match local structure padding/alignment expectations, we want
- * to do the rewrite as part of the extract, rather than extracting
- * into a temp file and slurping it back out. (The structure alignment
- * is currently correct for all platforms, and this isn't expected to
- * change, so we should be okay with having it already extracted.)
- *
- * Returns "true" on success.
- */
-bool dvmContinueOptimization(int fd, off_t dexOffset, long dexLength,
- const char* fileName, u4 modWhen, u4 crc, bool isBootstrap)
-{
- DexClassLookup* pClassLookup = NULL;
- IndexMapSet* pIndexMapSet = NULL;
- RegisterMapBuilder* pRegMapBuilder = NULL;
- bool doVerify, doOpt;
- u4 headerFlags = 0;
-
- if (gDvm.classVerifyMode == VERIFY_MODE_NONE)
- doVerify = false;
- else if (gDvm.classVerifyMode == VERIFY_MODE_REMOTE)
- doVerify = !isBootstrap;
- else /*if (gDvm.classVerifyMode == VERIFY_MODE_ALL)*/
- doVerify = true;
-
- if (gDvm.dexOptMode == OPTIMIZE_MODE_NONE)
- doOpt = false;
- else if (gDvm.dexOptMode == OPTIMIZE_MODE_VERIFIED)
- doOpt = doVerify;
- else /*if (gDvm.dexOptMode == OPTIMIZE_MODE_ALL)*/
- doOpt = true;
-
- LOGV("Continuing optimization (%s, isb=%d, vfy=%d, opt=%d)\n",
- fileName, isBootstrap, doVerify, doOpt);
-
- assert(dexOffset >= 0);
-
- /* quick test so we don't blow up on empty file */
- if (dexLength < (int) sizeof(DexHeader)) {
- LOGE("too small to be DEX\n");
- return false;
- }
- if (dexOffset < (int) sizeof(DexOptHeader)) {
- LOGE("not enough room for opt header\n");
- return false;
- }
-
- bool result = false;
-
- /*
- * Drop this into a global so we don't have to pass it around. We could
- * also add a field to DexFile, but since it only pertains to DEX
- * creation that probably doesn't make sense.
- */
- gDvm.optimizingBootstrapClass = isBootstrap;
-
- {
- /*
- * Map the entire file (so we don't have to worry about page
- * alignment). The expectation is that the output file contains
- * our DEX data plus room for a small header.
- */
- bool success;
- void* mapAddr;
- mapAddr = mmap(NULL, dexOffset + dexLength, PROT_READ|PROT_WRITE,
- MAP_SHARED, fd, 0);
- if (mapAddr == MAP_FAILED) {
- LOGE("unable to mmap DEX cache: %s\n", strerror(errno));
- goto bail;
- }
-
- /*
- * Rewrite the file. Byte reordering, structure realigning,
- * class verification, and bytecode optimization are all performed
- * here.
- *
- * In theory the file could change size and bits could shift around.
- * In practice this would be annoying to deal with, so the file
- * layout is designed so that it can always be rewritten in place.
- *
- * This sets "headerFlags" and creates the class lookup table as
- * part of doing the processing.
- */
- success = rewriteDex(((u1*) mapAddr) + dexOffset, dexLength,
- doVerify, doOpt, &headerFlags, &pClassLookup);
-
- if (success) {
- DvmDex* pDvmDex = NULL;
- u1* dexAddr = ((u1*) mapAddr) + dexOffset;
-
- if (dvmDexFileOpenPartial(dexAddr, dexLength, &pDvmDex) != 0) {
- LOGE("Unable to create DexFile\n");
- success = false;
- } else {
- /*
- * If configured to do so, scan the instructions, looking
- * for ways to reduce the size of the resolved-constant table.
- * This is done post-optimization, across the instructions
- * in all methods in all classes (even the ones that failed
- * to load).
- */
- pIndexMapSet = dvmRewriteConstants(pDvmDex);
-
- /*
- * If configured to do so, generate a full set of register
- * maps for all verified classes.
- */
- if (gDvm.generateRegisterMaps) {
- pRegMapBuilder = dvmGenerateRegisterMaps(pDvmDex);
- if (pRegMapBuilder == NULL) {
- LOGE("Failed generating register maps\n");
- success = false;
- }
- }
-
- DexHeader* pHeader = (DexHeader*)pDvmDex->pHeader;
- updateChecksum(dexAddr, dexLength, pHeader);
-
- dvmDexFileFree(pDvmDex);
- }
- }
-
- /* unmap the read-write version, forcing writes to disk */
- if (msync(mapAddr, dexOffset + dexLength, MS_SYNC) != 0) {
- LOGW("msync failed: %s\n", strerror(errno));
- // weird, but keep going
- }
-#if 1
- /*
- * This causes clean shutdown to fail, because we have loaded classes
- * that point into it. For the optimizer this isn't a problem,
- * because it's more efficient for the process to simply exit.
- * Exclude this code when doing clean shutdown for valgrind.
- */
- if (munmap(mapAddr, dexOffset + dexLength) != 0) {
- LOGE("munmap failed: %s\n", strerror(errno));
- goto bail;
- }
-#endif
-
- if (!success)
- goto bail;
- }
-
- /* get start offset, and adjust deps start for 64-bit alignment */
- off_t depsOffset, auxOffset, endOffset, adjOffset;
- int depsLength, auxLength;
- u4 optChecksum;
-
- depsOffset = lseek(fd, 0, SEEK_END);
- if (depsOffset < 0) {
- LOGE("lseek to EOF failed: %s\n", strerror(errno));
- goto bail;
- }
- adjOffset = (depsOffset + 7) & ~(0x07);
- if (adjOffset != depsOffset) {
- LOGV("Adjusting deps start from %d to %d\n",
- (int) depsOffset, (int) adjOffset);
- depsOffset = adjOffset;
- lseek(fd, depsOffset, SEEK_SET);
- }
-
- /*
- * Append the dependency list.
- */
- if (writeDependencies(fd, modWhen, crc) != 0) {
- LOGW("Failed writing dependencies\n");
- goto bail;
- }
-
- /* compute deps length, then adjust aux start for 64-bit alignment */
- auxOffset = lseek(fd, 0, SEEK_END);
- depsLength = auxOffset - depsOffset;
-
- adjOffset = (auxOffset + 7) & ~(0x07);
- if (adjOffset != auxOffset) {
- LOGV("Adjusting aux start from %d to %d\n",
- (int) auxOffset, (int) adjOffset);
- auxOffset = adjOffset;
- lseek(fd, auxOffset, SEEK_SET);
- }
-
- /*
- * Append any auxillary pre-computed data structures.
- */
- if (!writeAuxData(fd, pClassLookup, pIndexMapSet, pRegMapBuilder)) {
- LOGW("Failed writing aux data\n");
- goto bail;
- }
-
- endOffset = lseek(fd, 0, SEEK_END);
- auxLength = endOffset - auxOffset;
-
- /* compute checksum from start of deps to end of aux area */
- if (!computeFileChecksum(fd, depsOffset,
- (auxOffset+auxLength) - depsOffset, &optChecksum))
- {
- goto bail;
- }
-
- /*
- * Output the "opt" header with all values filled in and a correct
- * magic number.
- */
- DexOptHeader optHdr;
- memset(&optHdr, 0xff, sizeof(optHdr));
- memcpy(optHdr.magic, DEX_OPT_MAGIC, 4);
- memcpy(optHdr.magic+4, DEX_OPT_MAGIC_VERS, 4);
- optHdr.dexOffset = (u4) dexOffset;
- optHdr.dexLength = (u4) dexLength;
- optHdr.depsOffset = (u4) depsOffset;
- optHdr.depsLength = (u4) depsLength;
- optHdr.auxOffset = (u4) auxOffset;
- optHdr.auxLength = (u4) auxLength;
-
- optHdr.flags = headerFlags;
- optHdr.checksum = optChecksum;
-
- ssize_t actual;
- lseek(fd, 0, SEEK_SET);
- actual = write(fd, &optHdr, sizeof(optHdr));
- if (actual != sizeof(optHdr)) {
- logFailedWrite(sizeof(optHdr), actual, "opt header", errno);
- goto bail;
- }
-
- LOGV("Successfully wrote DEX header\n");
- result = true;
-
- //dvmRegisterMapDumpStats();
-
-bail:
- dvmFreeIndexMapSet(pIndexMapSet);
- dvmFreeRegisterMapBuilder(pRegMapBuilder);
- free(pClassLookup);
- return result;
-}
-
-
-/*
- * Get the cache file name from a ClassPathEntry.
- */
-static const char* getCacheFileName(const ClassPathEntry* cpe)
-{
- switch (cpe->kind) {
- case kCpeJar:
- return dvmGetJarFileCacheFileName((JarFile*) cpe->ptr);
- case kCpeDex:
- return dvmGetRawDexFileCacheFileName((RawDexFile*) cpe->ptr);
- default:
- LOGE("DexOpt: unexpected cpe kind %d\n", cpe->kind);
- dvmAbort();
- return NULL;
- }
-}
-
-/*
- * Get the SHA-1 signature.
- */
-static const u1* getSignature(const ClassPathEntry* cpe)
-{
- DvmDex* pDvmDex;
-
- switch (cpe->kind) {
- case kCpeJar:
- pDvmDex = dvmGetJarFileDex((JarFile*) cpe->ptr);
- break;
- case kCpeDex:
- pDvmDex = dvmGetRawDexFileDex((RawDexFile*) cpe->ptr);
- break;
- default:
- LOGE("unexpected cpe kind %d\n", cpe->kind);
- dvmAbort();
- pDvmDex = NULL; // make gcc happy
- }
-
- assert(pDvmDex != NULL);
- return pDvmDex->pDexFile->pHeader->signature;
-}
-
-
-/*
- * Dependency layout:
- * 4b Source file modification time, in seconds since 1970 UTC
- * 4b CRC-32 from Zip entry, or Adler32 from source DEX header
- * 4b Dalvik VM build number
- * 4b Number of dependency entries that follow
- * Dependency entries:
- * 4b Name length (including terminating null)
- * var Full path of cache entry (null terminated)
- * 20b SHA-1 signature from source DEX file
- *
- * If this changes, update DEX_OPT_MAGIC_VERS.
- */
-static const size_t kMinDepSize = 4 * 4;
-static const size_t kMaxDepSize = 4 * 4 + 1024; // sanity check
-
-/*
- * Read the "opt" header, verify it, then read the dependencies section
- * and verify that data as well.
- *
- * If "sourceAvail" is "true", this will verify that "modWhen" and "crc"
- * match up with what is stored in the header. If they don't, we reject
- * the file so that it can be recreated from the updated original. If
- * "sourceAvail" isn't set, e.g. for a .odex file, we ignore these arguments.
- *
- * On successful return, the file will be seeked immediately past the
- * "opt" header.
- */
-bool dvmCheckOptHeaderAndDependencies(int fd, bool sourceAvail, u4 modWhen,
- u4 crc, bool expectVerify, bool expectOpt)
-{
- DexOptHeader optHdr;
- u1* depData = NULL;
- const u1* magic;
- off_t posn;
- int result = false;
- ssize_t actual;
-
- /*
- * Start at the start. The "opt" header, when present, will always be
- * the first thing in the file.
- */
- if (lseek(fd, 0, SEEK_SET) != 0) {
- LOGE("DexOpt: failed to seek to start of file: %s\n", strerror(errno));
- goto bail;
- }
-
- /*
- * Read and do trivial verification on the opt header. The header is
- * always in host byte order.
- */
- if (read(fd, &optHdr, sizeof(optHdr)) != sizeof(optHdr)) {
- LOGE("DexOpt: failed reading opt header: %s\n", strerror(errno));
- goto bail;
- }
-
- magic = optHdr.magic;
- if (memcmp(magic, DEX_OPT_MAGIC, 4) != 0) {
- /* not a DEX file, or previous attempt was interrupted */
- LOGD("DexOpt: incorrect opt magic number (0x%02x %02x %02x %02x)\n",
- magic[0], magic[1], magic[2], magic[3]);
- goto bail;
- }
- if (memcmp(magic+4, DEX_OPT_MAGIC_VERS, 4) != 0) {
- LOGW("DexOpt: stale opt version (0x%02x %02x %02x %02x)\n",
- magic[4], magic[5], magic[6], magic[7]);
- goto bail;
- }
- if (optHdr.depsLength < kMinDepSize || optHdr.depsLength > kMaxDepSize) {
- LOGW("DexOpt: weird deps length %d, bailing\n", optHdr.depsLength);
- goto bail;
- }
-
- /*
- * Do the header flags match up with what we want?
- *
- * This is useful because it allows us to automatically regenerate
- * a file when settings change (e.g. verification is now mandatory),
- * but can cause difficulties if the bootstrap classes we depend upon
- * were handled differently than the current options specify. We get
- * upset because they're not verified or optimized, but we're not able
- * to regenerate them because the installer won't let us.
- *
- * (This is also of limited value when !sourceAvail.)
- *
- * So, for now, we essentially ignore "expectVerify" and "expectOpt"
- * by limiting the match mask.
- *
- * The only thing we really can't handle is incorrect byte-ordering.
- */
- const u4 matchMask = DEX_OPT_FLAG_BIG;
- u4 expectedFlags = 0;
-#if __BYTE_ORDER != __LITTLE_ENDIAN
- expectedFlags |= DEX_OPT_FLAG_BIG;
-#endif
- if (expectVerify)
- expectedFlags |= DEX_FLAG_VERIFIED;
- if (expectOpt)
- expectedFlags |= DEX_OPT_FLAG_FIELDS | DEX_OPT_FLAG_INVOCATIONS;
- if ((expectedFlags & matchMask) != (optHdr.flags & matchMask)) {
- LOGI("DexOpt: header flag mismatch (0x%02x vs 0x%02x, mask=0x%02x)\n",
- expectedFlags, optHdr.flags, matchMask);
- goto bail;
- }
-
- posn = lseek(fd, optHdr.depsOffset, SEEK_SET);
- if (posn < 0) {
- LOGW("DexOpt: seek to deps failed: %s\n", strerror(errno));
- goto bail;
- }
-
- /*
- * Read all of the dependency stuff into memory.
- */
- depData = (u1*) malloc(optHdr.depsLength);
- if (depData == NULL) {
- LOGW("DexOpt: unable to allocate %d bytes for deps\n",
- optHdr.depsLength);
- goto bail;
- }
- actual = read(fd, depData, optHdr.depsLength);
- if (actual != (ssize_t) optHdr.depsLength) {
- LOGW("DexOpt: failed reading deps: %d of %d (err=%s)\n",
- (int) actual, optHdr.depsLength, strerror(errno));
- goto bail;
- }
-
- /*
- * Verify simple items.
- */
- const u1* ptr;
- u4 val;
-
- ptr = depData;
- val = read4LE(&ptr);
- if (sourceAvail && val != modWhen) {
- LOGI("DexOpt: source file mod time mismatch (%08x vs %08x)\n",
- val, modWhen);
- goto bail;
- }
- val = read4LE(&ptr);
- if (sourceAvail && val != crc) {
- LOGI("DexOpt: source file CRC mismatch (%08x vs %08x)\n", val, crc);
- goto bail;
- }
- val = read4LE(&ptr);
- if (val != DALVIK_VM_BUILD) {
- LOGD("DexOpt: VM build version mismatch (%d vs %d)\n",
- val, DALVIK_VM_BUILD);
- goto bail;
- }
-
- /*
- * Verify dependencies on other cached DEX files. It must match
- * exactly with what is currently defined in the bootclasspath.
- */
- ClassPathEntry* cpe;
- u4 numDeps;
-
- numDeps = read4LE(&ptr);
- LOGV("+++ DexOpt: numDeps = %d\n", numDeps);
- for (cpe = gDvm.bootClassPath; cpe->ptr != NULL; cpe++) {
- const char* cacheFileName = getCacheFileName(cpe);
- const u1* signature = getSignature(cpe);
- size_t len = strlen(cacheFileName) +1;
- u4 storedStrLen;
-
- if (numDeps == 0) {
- /* more entries in bootclasspath than in deps list */
- LOGI("DexOpt: not all deps represented\n");
- goto bail;
- }
-
- storedStrLen = read4LE(&ptr);
- if (len != storedStrLen ||
- strcmp(cacheFileName, (const char*) ptr) != 0)
- {
- LOGI("DexOpt: mismatch dep name: '%s' vs. '%s'\n",
- cacheFileName, ptr);
- goto bail;
- }
-
- ptr += storedStrLen;
-
- if (memcmp(signature, ptr, kSHA1DigestLen) != 0) {
- LOGI("DexOpt: mismatch dep signature for '%s'\n", cacheFileName);
- goto bail;
- }
- ptr += kSHA1DigestLen;
-
- LOGV("DexOpt: dep match on '%s'\n", cacheFileName);
-
- numDeps--;
- }
-
- if (numDeps != 0) {
- /* more entries in deps list than in classpath */
- LOGI("DexOpt: Some deps went away\n");
- goto bail;
- }
-
- // consumed all data and no more?
- if (ptr != depData + optHdr.depsLength) {
- LOGW("DexOpt: Spurious dep data? %d vs %d\n",
- (int) (ptr - depData), optHdr.depsLength);
- assert(false);
- }
-
- result = true;
-
-bail:
- free(depData);
- return result;
-}
-
-/*
- * Write the dependency info to "fd" at the current file position.
- */
-static int writeDependencies(int fd, u4 modWhen, u4 crc)
-{
- u1* buf = NULL;
- ssize_t actual;
- int result = -1;
- ssize_t bufLen;
- ClassPathEntry* cpe;
- int i, numDeps;
-
- /*
- * Count up the number of completed entries in the bootclasspath.
- */
- numDeps = 0;
- bufLen = 0;
- for (cpe = gDvm.bootClassPath; cpe->ptr != NULL; cpe++) {
- const char* cacheFileName = getCacheFileName(cpe);
- LOGV("+++ DexOpt: found dep '%s'\n", cacheFileName);
-
- numDeps++;
- bufLen += strlen(cacheFileName) +1;
- }
-
- bufLen += 4*4 + numDeps * (4+kSHA1DigestLen);
-
- buf = malloc(bufLen);
-
- set4LE(buf+0, modWhen);
- set4LE(buf+4, crc);
- set4LE(buf+8, DALVIK_VM_BUILD);
- set4LE(buf+12, numDeps);
-
- // TODO: do we want to add dvmGetInlineOpsTableLength() here? Won't
- // help us if somebody replaces an existing entry, but it'd catch
- // additions/removals.
-
- u1* ptr = buf + 4*4;
- for (cpe = gDvm.bootClassPath; cpe->ptr != NULL; cpe++) {
- const char* cacheFileName = getCacheFileName(cpe);
- const u1* signature = getSignature(cpe);
- int len = strlen(cacheFileName) +1;
-
- if (ptr + 4 + len + kSHA1DigestLen > buf + bufLen) {
- LOGE("DexOpt: overran buffer\n");
- dvmAbort();
- }
-
- set4LE(ptr, len);
- ptr += 4;
- memcpy(ptr, cacheFileName, len);
- ptr += len;
- memcpy(ptr, signature, kSHA1DigestLen);
- ptr += kSHA1DigestLen;
- }
-
- assert(ptr == buf + bufLen);
-
- actual = write(fd, buf, bufLen);
- if (actual != bufLen) {
- result = (errno != 0) ? errno : -1;
- logFailedWrite(bufLen, actual, "dep info", errno);
- } else {
- result = 0;
- }
-
- free(buf);
- return result;
-}
-
-
-/*
- * Write a block of data in "chunk" format.
- *
- * The chunk header fields are always in "native" byte order. If "size"
- * is not a multiple of 8 bytes, the data area is padded out.
- */
-static bool writeChunk(int fd, u4 type, const void* data, size_t size)
-{
- ssize_t actual;
- union { /* save a syscall by grouping these together */
- char raw[8];
- struct {
- u4 type;
- u4 size;
- } ts;
- } header;
-
- assert(sizeof(header) == 8);
-
- LOGV("Writing chunk, type=%.4s size=%d\n", (char*) &type, size);
-
- header.ts.type = type;
- header.ts.size = (u4) size;
- actual = write(fd, &header, sizeof(header));
- if (actual != sizeof(header)) {
- logFailedWrite(size, actual, "aux chunk header write", errno);
- return false;
- }
-
- if (size > 0) {
- actual = write(fd, data, size);
- if (actual != (ssize_t) size) {
- logFailedWrite(size, actual, "aux chunk write", errno);
- return false;
- }
- }
-
- /* if necessary, pad to 64-bit alignment */
- if ((size & 7) != 0) {
- int padSize = 8 - (size & 7);
- LOGV("size was %d, inserting %d pad bytes\n", size, padSize);
- lseek(fd, padSize, SEEK_CUR);
- }
-
- assert( ((int)lseek(fd, 0, SEEK_CUR) & 7) == 0);
-
- return true;
-}
-
-/*
- * Write aux data.
- *
- * We have different pieces, some of which may be optional. To make the
- * most effective use of space, we use a "chunk" format, with a 4-byte
- * type and a 4-byte length. We guarantee 64-bit alignment for the data,
- * so it can be used directly when the file is mapped for reading.
- */
-static bool writeAuxData(int fd, const DexClassLookup* pClassLookup,
- const IndexMapSet* pIndexMapSet, const RegisterMapBuilder* pRegMapBuilder)
-{
- /* pre-computed class lookup hash table */
- if (!writeChunk(fd, (u4) kDexChunkClassLookup,
- pClassLookup, pClassLookup->size))
- {
- return false;
- }
-
- /* remapped constants (optional) */
- if (pIndexMapSet != NULL) {
- if (!writeChunk(fd, pIndexMapSet->chunkType,
- pIndexMapSet->chunkData, pIndexMapSet->chunkDataLen))
- {
- return false;
- }
- }
-
- /* register maps (optional) */
- if (pRegMapBuilder != NULL) {
- if (!writeChunk(fd, (u4) kDexChunkRegisterMaps,
- pRegMapBuilder->data, pRegMapBuilder->size))
- {
- return false;
- }
- }
-
- /* write the end marker */
- if (!writeChunk(fd, (u4) kDexChunkEnd, NULL, 0)) {
- return false;
- }
-
- return true;
-}
-
-/*
- * Log a failed write.
- */
-static void logFailedWrite(size_t expected, ssize_t actual, const char* msg,
- int err)
-{
- LOGE("Write failed: %s (%d of %d): %s\n",
- msg, (int)actual, (int)expected, strerror(err));
-}
-
-/*
- * Compute a checksum on a piece of an open file.
- *
- * File will be positioned at end of checksummed area.
- *
- * Returns "true" on success.
- */
-static bool computeFileChecksum(int fd, off_t start, size_t length, u4* pSum)
-{
- unsigned char readBuf[8192];
- ssize_t actual;
- uLong adler;
-
- if (lseek(fd, start, SEEK_SET) != start) {
- LOGE("Unable to seek to start of checksum area (%ld): %s\n",
- (long) start, strerror(errno));
- return false;
- }
-
- adler = adler32(0L, Z_NULL, 0);
-
- while (length != 0) {
- size_t wanted = (length < sizeof(readBuf)) ? length : sizeof(readBuf);
- actual = read(fd, readBuf, wanted);
- if (actual <= 0) {
- LOGE("Read failed (%d) while computing checksum (len=%zu): %s\n",
- (int) actual, length, strerror(errno));
- return false;
- }
-
- adler = adler32(adler, readBuf, actual);
-
- length -= actual;
- }
-
- *pSum = adler;
- return true;
-}
-
-
-/*
- * ===========================================================================
- * Optimizations
- * ===========================================================================
- */
-
-/*
- * Perform in-place rewrites on a memory-mapped DEX file.
- *
- * This happens in a short-lived child process, so we can go nutty with
- * loading classes and allocating memory.
- */
-static bool rewriteDex(u1* addr, int len, bool doVerify, bool doOpt,
- u4* pHeaderFlags, DexClassLookup** ppClassLookup)
-{
- u8 prepWhen, loadWhen, verifyWhen, optWhen;
- DvmDex* pDvmDex = NULL;
- bool result = false;
-
- *pHeaderFlags = 0;
-
- LOGV("+++ swapping bytes\n");
- if (dexFixByteOrdering(addr, len) != 0)
- goto bail;
-#if __BYTE_ORDER != __LITTLE_ENDIAN
- *pHeaderFlags |= DEX_OPT_FLAG_BIG;
-#endif
-
- /*
- * Now that the DEX file can be read directly, create a DexFile for it.
- */
- if (dvmDexFileOpenPartial(addr, len, &pDvmDex) != 0) {
- LOGE("Unable to create DexFile\n");
- goto bail;
- }
-
- /*
- * Create the class lookup table.
- */
- //startWhen = dvmGetRelativeTimeUsec();
- *ppClassLookup = dexCreateClassLookup(pDvmDex->pDexFile);
- if (*ppClassLookup == NULL)
- goto bail;
-
- /*
- * Bail out early if they don't want The Works. The current implementation
- * doesn't fork a new process if this flag isn't set, so we really don't
- * want to continue on with the crazy class loading.
- */
- if (!doVerify && !doOpt) {
- result = true;
- goto bail;
- }
-
- /* this is needed for the next part */
- pDvmDex->pDexFile->pClassLookup = *ppClassLookup;
-
- prepWhen = dvmGetRelativeTimeUsec();
-
- /*
- * Load all classes found in this DEX file. If they fail to load for
- * some reason, they won't get verified (which is as it should be).
- */
- if (!loadAllClasses(pDvmDex))
- goto bail;
- loadWhen = dvmGetRelativeTimeUsec();
-
- /*
- * Verify all classes in the DEX file. Export the "is verified" flag
- * to the DEX file we're creating.
- */
- if (doVerify) {
- dvmVerifyAllClasses(pDvmDex->pDexFile);
- *pHeaderFlags |= DEX_FLAG_VERIFIED;
- }
- verifyWhen = dvmGetRelativeTimeUsec();
-
- /*
- * Optimize the classes we successfully loaded. If the opt mode is
- * OPTIMIZE_MODE_VERIFIED, each class must have been successfully
- * verified or we'll skip it.
- */
-#ifndef PROFILE_FIELD_ACCESS
- if (doOpt) {
- optimizeLoadedClasses(pDvmDex->pDexFile);
- *pHeaderFlags |= DEX_OPT_FLAG_FIELDS | DEX_OPT_FLAG_INVOCATIONS;
- }
-#endif
- optWhen = dvmGetRelativeTimeUsec();
-
- LOGD("DexOpt: load %dms, verify %dms, opt %dms\n",
- (int) (loadWhen - prepWhen) / 1000,
- (int) (verifyWhen - loadWhen) / 1000,
- (int) (optWhen - verifyWhen) / 1000);
-
- result = true;
-
-bail:
- /* free up storage */
- dvmDexFileFree(pDvmDex);
-
- return result;
-}
-
-/*
- * Update the Adler-32 checksum stored in the DEX file. This covers the
- * swapped and optimized DEX data, but does not include the opt header
- * or auxillary data.
- */
-static void updateChecksum(u1* addr, int len, DexHeader* pHeader)
-{
- /*
- * Rewrite the checksum. We leave the SHA-1 signature alone.
- */
- uLong adler = adler32(0L, Z_NULL, 0);
- const int nonSum = sizeof(pHeader->magic) + sizeof(pHeader->checksum);
-
- adler = adler32(adler, addr + nonSum, len - nonSum);
- pHeader->checksum = adler;
-}
-
-/*
- * Try to load all classes in the specified DEX. If they have some sort
- * of broken dependency, e.g. their superclass lives in a different DEX
- * that wasn't previously loaded into the bootstrap class path, loading
- * will fail. This is the desired behavior.
- *
- * We have no notion of class loader at this point, so we load all of
- * the classes with the bootstrap class loader. It turns out this has
- * exactly the behavior we want, and has no ill side effects because we're
- * running in a separate process and anything we load here will be forgotten.
- *
- * We set the CLASS_MULTIPLE_DEFS flag here if we see multiple definitions.
- * This works because we only call here as part of optimization / pre-verify,
- * not during verification as part of loading a class into a running VM.
- *
- * This returns "false" if the world is too screwed up to do anything
- * useful at all.
- */
-static bool loadAllClasses(DvmDex* pDvmDex)
-{
- u4 count = pDvmDex->pDexFile->pHeader->classDefsSize;
- u4 idx;
- int loaded = 0;
-
- LOGV("DexOpt: +++ trying to load %d classes\n", count);
-
- dvmSetBootPathExtraDex(pDvmDex);
-
- /*
- * We have some circularity issues with Class and Object that are most
- * easily avoided by ensuring that Object is never the first thing we
- * try to find. Take care of that here. (We only need to do this when
- * loading classes from the DEX file that contains Object, and only
- * when Object comes first in the list, but it costs very little to
- * do it in all cases.)
- */
- if (dvmFindSystemClass("Ljava/lang/Class;") == NULL) {
- LOGE("ERROR: java.lang.Class does not exist!\n");
- return false;
- }
-
- for (idx = 0; idx < count; idx++) {
- const DexClassDef* pClassDef;
- const char* classDescriptor;
- ClassObject* newClass;
-
- pClassDef = dexGetClassDef(pDvmDex->pDexFile, idx);
- classDescriptor =
- dexStringByTypeIdx(pDvmDex->pDexFile, pClassDef->classIdx);
-
- LOGV("+++ loading '%s'", classDescriptor);
- //newClass = dvmDefineClass(pDexFile, classDescriptor,
- // NULL);
- newClass = dvmFindSystemClassNoInit(classDescriptor);
- if (newClass == NULL) {
- LOGV("DexOpt: failed loading '%s'\n", classDescriptor);
- dvmClearOptException(dvmThreadSelf());
- } else if (newClass->pDvmDex != pDvmDex) {
- /*
- * We don't load the new one, and we tag the first one found
- * with the "multiple def" flag so the resolver doesn't try
- * to make it available.
- */
- LOGD("DexOpt: '%s' has an earlier definition; blocking out\n",
- classDescriptor);
- SET_CLASS_FLAG(newClass, CLASS_MULTIPLE_DEFS);
- } else {
- loaded++;
- }
- }
- LOGV("DexOpt: +++ successfully loaded %d classes\n", loaded);
-
- dvmSetBootPathExtraDex(NULL);
- return true;
-}
-
-
-/*
- * Create a table of inline substitutions.
- *
- * TODO: this is currently just a linear array. We will want to put this
- * into a hash table as the list size increases.
- */
-static InlineSub* createInlineSubsTable(void)
-{
- const InlineOperation* ops = dvmGetInlineOpsTable();
- const int count = dvmGetInlineOpsTableLength();
- InlineSub* table;
- Method* method;
- ClassObject* clazz;
- int i, tableIndex;
-
- /*
- * Allocate for optimism: one slot per entry, plus an end-of-list marker.
- */
- table = malloc(sizeof(InlineSub) * (count+1));
-
- tableIndex = 0;
- for (i = 0; i < count; i++) {
- clazz = dvmFindClassNoInit(ops[i].classDescriptor, NULL);
- if (clazz == NULL) {
- LOGV("DexOpt: can't inline for class '%s': not found\n",
- ops[i].classDescriptor);
- dvmClearOptException(dvmThreadSelf());
- } else {
- /*
- * Method could be virtual or direct. Try both. Don't use
- * the "hier" versions.
- */
- method = dvmFindDirectMethodByDescriptor(clazz, ops[i].methodName,
- ops[i].methodSignature);
- if (method == NULL)
- method = dvmFindVirtualMethodByDescriptor(clazz, ops[i].methodName,
- ops[i].methodSignature);
- if (method == NULL) {
- LOGW("DexOpt: can't inline %s.%s %s: method not found\n",
- ops[i].classDescriptor, ops[i].methodName,
- ops[i].methodSignature);
- } else {
- if (!dvmIsFinalClass(clazz) && !dvmIsFinalMethod(method)) {
- LOGW("DexOpt: WARNING: inline op on non-final class/method "
- "%s.%s\n",
- clazz->descriptor, method->name);
- /* fail? */
- }
- if (dvmIsSynchronizedMethod(method) ||
- dvmIsDeclaredSynchronizedMethod(method))
- {
- LOGW("DexOpt: WARNING: inline op on synchronized method "
- "%s.%s\n",
- clazz->descriptor, method->name);
- /* fail? */
- }
-
- table[tableIndex].method = method;
- table[tableIndex].inlineIdx = i;
- tableIndex++;
-
- LOGV("DexOpt: will inline %d: %s.%s %s\n", i,
- ops[i].classDescriptor, ops[i].methodName,
- ops[i].methodSignature);
- }
- }
- }
-
- /* mark end of table */
- table[tableIndex].method = NULL;
- LOGV("DexOpt: inline table has %d entries\n", tableIndex);
-
- return table;
-}
-
-/*
- * Run through all classes that were successfully loaded from this DEX
- * file and optimize their code sections.
- */
-static void optimizeLoadedClasses(DexFile* pDexFile)
-{
- u4 count = pDexFile->pHeader->classDefsSize;
- u4 idx;
- InlineSub* inlineSubs = NULL;
-
- LOGV("DexOpt: +++ optimizing up to %d classes\n", count);
- assert(gDvm.dexOptMode != OPTIMIZE_MODE_NONE);
-
- inlineSubs = createInlineSubsTable();
-
- for (idx = 0; idx < count; idx++) {
- const DexClassDef* pClassDef;
- const char* classDescriptor;
- ClassObject* clazz;
-
- pClassDef = dexGetClassDef(pDexFile, idx);
- classDescriptor = dexStringByTypeIdx(pDexFile, pClassDef->classIdx);
-
- /* all classes are loaded into the bootstrap class loader */
- clazz = dvmLookupClass(classDescriptor, NULL, false);
- if (clazz != NULL) {
- if ((pClassDef->accessFlags & CLASS_ISPREVERIFIED) == 0 &&
- gDvm.dexOptMode == OPTIMIZE_MODE_VERIFIED)
- {
- LOGV("DexOpt: not optimizing '%s': not verified\n",
- classDescriptor);
- } else if (clazz->pDvmDex->pDexFile != pDexFile) {
- /* shouldn't be here -- verifier should have caught */
- LOGD("DexOpt: not optimizing '%s': multiple definitions\n",
- classDescriptor);
- } else {
- optimizeClass(clazz, inlineSubs);
-
- /* set the flag whether or not we actually did anything */
- ((DexClassDef*)pClassDef)->accessFlags |=
- CLASS_ISOPTIMIZED;
- }
- } else {
- LOGV("DexOpt: not optimizing unavailable class '%s'\n",
- classDescriptor);
- }
- }
-
- free(inlineSubs);
-}
-
-/*
- * Optimize the specified class.
- */
-static void optimizeClass(ClassObject* clazz, const InlineSub* inlineSubs)
-{
- int i;
-
- for (i = 0; i < clazz->directMethodCount; i++) {
- if (!optimizeMethod(&clazz->directMethods[i], inlineSubs))
- goto fail;
- }
- for (i = 0; i < clazz->virtualMethodCount; i++) {
- if (!optimizeMethod(&clazz->virtualMethods[i], inlineSubs))
- goto fail;
- }
-
- return;
-
-fail:
- LOGV("DexOpt: ceasing optimization attempts on %s\n", clazz->descriptor);
-}
-
-/*
- * Optimize instructions in a method.
- *
- * Returns "true" if all went well, "false" if we bailed out early when
- * something failed.
- */
-static bool optimizeMethod(Method* method, const InlineSub* inlineSubs)
-{
- u4 insnsSize;
- u2* insns;
- u2 inst;
-
- if (dvmIsNativeMethod(method) || dvmIsAbstractMethod(method))
- return true;
-
- insns = (u2*) method->insns;
- assert(insns != NULL);
- insnsSize = dvmGetMethodInsnsSize(method);
-
- while (insnsSize > 0) {
- int width;
-
- inst = *insns & 0xff;
-
- switch (inst) {
- case OP_IGET:
- case OP_IGET_BOOLEAN:
- case OP_IGET_BYTE:
- case OP_IGET_CHAR:
- case OP_IGET_SHORT:
- rewriteInstField(method, insns, OP_IGET_QUICK);
- break;
- case OP_IGET_WIDE:
- rewriteInstField(method, insns, OP_IGET_WIDE_QUICK);
- break;
- case OP_IGET_OBJECT:
- rewriteInstField(method, insns, OP_IGET_OBJECT_QUICK);
- break;
- case OP_IPUT:
- case OP_IPUT_BOOLEAN:
- case OP_IPUT_BYTE:
- case OP_IPUT_CHAR:
- case OP_IPUT_SHORT:
- rewriteInstField(method, insns, OP_IPUT_QUICK);
- break;
- case OP_IPUT_WIDE:
- rewriteInstField(method, insns, OP_IPUT_WIDE_QUICK);
- break;
- case OP_IPUT_OBJECT:
- rewriteInstField(method, insns, OP_IPUT_OBJECT_QUICK);
- break;
-
- case OP_INVOKE_VIRTUAL:
- if (!rewriteExecuteInline(method, insns, METHOD_VIRTUAL,inlineSubs))
- {
- if (!rewriteVirtualInvoke(method, insns, OP_INVOKE_VIRTUAL_QUICK))
- return false;
- }
- break;
- case OP_INVOKE_VIRTUAL_RANGE:
- if (!rewriteExecuteInlineRange(method, insns, METHOD_VIRTUAL,
- inlineSubs))
- {
- if (!rewriteVirtualInvoke(method, insns,
- OP_INVOKE_VIRTUAL_QUICK_RANGE))
- {
- return false;
- }
- }
- break;
- case OP_INVOKE_SUPER:
- if (!rewriteVirtualInvoke(method, insns, OP_INVOKE_SUPER_QUICK))
- return false;
- break;
- case OP_INVOKE_SUPER_RANGE:
- if (!rewriteVirtualInvoke(method, insns, OP_INVOKE_SUPER_QUICK_RANGE))
- return false;
- break;
-
- case OP_INVOKE_DIRECT:
- if (!rewriteExecuteInline(method, insns, METHOD_DIRECT, inlineSubs))
- {
- if (!rewriteEmptyDirectInvoke(method, insns))
- return false;
- }
- break;
- case OP_INVOKE_DIRECT_RANGE:
- rewriteExecuteInlineRange(method, insns, METHOD_DIRECT, inlineSubs);
- break;
-
- case OP_INVOKE_STATIC:
- rewriteExecuteInline(method, insns, METHOD_STATIC, inlineSubs);
- break;
- case OP_INVOKE_STATIC_RANGE:
- rewriteExecuteInlineRange(method, insns, METHOD_STATIC, inlineSubs);
- break;
-
- default:
- // ignore this instruction
- ;
- }
-
- if (*insns == kPackedSwitchSignature) {
- width = 4 + insns[1] * 2;
- } else if (*insns == kSparseSwitchSignature) {
- width = 2 + insns[1] * 4;
- } else if (*insns == kArrayDataSignature) {
- u2 elemWidth = insns[1];
- u4 len = insns[2] | (((u4)insns[3]) << 16);
- width = 4 + (elemWidth * len + 1) / 2;
- } else {
- width = dexGetInstrWidthAbs(gDvm.instrWidth, inst);
- }
- assert(width > 0);
-
- insns += width;
- insnsSize -= width;
- }
-
- assert(insnsSize == 0);
- return true;
-}
-
-
-/*
- * If "referrer" and "resClass" don't come from the same DEX file, and
- * the DEX we're working on is not destined for the bootstrap class path,
- * tweak the class loader so package-access checks work correctly.
- *
- * Only do this if we're doing pre-verification or optimization.
- */
-static void tweakLoader(ClassObject* referrer, ClassObject* resClass)
-{
- if (!gDvm.optimizing)
- return;
- assert(referrer->classLoader == NULL);
- assert(resClass->classLoader == NULL);
-
- if (!gDvm.optimizingBootstrapClass) {
- /* class loader for an array class comes from element type */
- if (dvmIsArrayClass(resClass))
- resClass = resClass->elementClass;
- if (referrer->pDvmDex != resClass->pDvmDex)
- resClass->classLoader = (Object*) 0xdead3333;
- }
-}
-
-/*
- * Undo the effects of tweakLoader.
- */
-static void untweakLoader(ClassObject* referrer, ClassObject* resClass)
-{
- if (!gDvm.optimizing || gDvm.optimizingBootstrapClass)
- return;
-
- if (dvmIsArrayClass(resClass))
- resClass = resClass->elementClass;
- resClass->classLoader = NULL;
-}
-
-
-/*
- * Alternate version of dvmResolveClass for use with verification and
- * optimization. Performs access checks on every resolve, and refuses
- * to acknowledge the existence of classes defined in more than one DEX
- * file.
- *
- * Exceptions caused by failures are cleared before returning.
- *
- * On failure, returns NULL, and sets *pFailure if pFailure is not NULL.
- */
-ClassObject* dvmOptResolveClass(ClassObject* referrer, u4 classIdx,
- VerifyError* pFailure)
-{
- DvmDex* pDvmDex = referrer->pDvmDex;
- ClassObject* resClass;
-
- /*
- * Check the table first. If not there, do the lookup by name.
- */
- resClass = dvmDexGetResolvedClass(pDvmDex, classIdx);
- if (resClass == NULL) {
- const char* className = dexStringByTypeIdx(pDvmDex->pDexFile, classIdx);
- if (className[0] != '\0' && className[1] == '\0') {
- /* primitive type */
- resClass = dvmFindPrimitiveClass(className[0]);
- } else {
- resClass = dvmFindClassNoInit(className, referrer->classLoader);
- }
- if (resClass == NULL) {
- /* not found, exception should be raised */
- LOGV("DexOpt: class %d (%s) not found\n",
- classIdx,
- dexStringByTypeIdx(pDvmDex->pDexFile, classIdx));
- if (pFailure != NULL) {
- /* dig through the wrappers to find the original failure */
- Object* excep = dvmGetException(dvmThreadSelf());
- while (true) {
- Object* cause = dvmGetExceptionCause(excep);
- if (cause == NULL)
- break;
- excep = cause;
- }
- if (strcmp(excep->clazz->descriptor,
- "Ljava/lang/IncompatibleClassChangeError;") == 0)
- {
- *pFailure = VERIFY_ERROR_CLASS_CHANGE;
- } else {
- *pFailure = VERIFY_ERROR_NO_CLASS;
- }
- }
- dvmClearOptException(dvmThreadSelf());
- return NULL;
- }
-
- /*
- * Add it to the resolved table so we're faster on the next lookup.
- */
- dvmDexSetResolvedClass(pDvmDex, classIdx, resClass);
- }
-
- /* multiple definitions? */
- if (IS_CLASS_FLAG_SET(resClass, CLASS_MULTIPLE_DEFS)) {
- LOGI("DexOpt: not resolving ambiguous class '%s'\n",
- resClass->descriptor);
- if (pFailure != NULL)
- *pFailure = VERIFY_ERROR_NO_CLASS;
- return NULL;
- }
-
- /* access allowed? */
- tweakLoader(referrer, resClass);
- bool allowed = dvmCheckClassAccess(referrer, resClass);
- untweakLoader(referrer, resClass);
- if (!allowed) {
- LOGW("DexOpt: resolve class illegal access: %s -> %s\n",
- referrer->descriptor, resClass->descriptor);
- if (pFailure != NULL)
- *pFailure = VERIFY_ERROR_ACCESS_CLASS;
- return NULL;
- }
-
- return resClass;
-}
-
-/*
- * Alternate version of dvmResolveInstField().
- *
- * On failure, returns NULL, and sets *pFailure if pFailure is not NULL.
- */
-InstField* dvmOptResolveInstField(ClassObject* referrer, u4 ifieldIdx,
- VerifyError* pFailure)
-{
- DvmDex* pDvmDex = referrer->pDvmDex;
- InstField* resField;
-
- resField = (InstField*) dvmDexGetResolvedField(pDvmDex, ifieldIdx);
- if (resField == NULL) {
- const DexFieldId* pFieldId;
- ClassObject* resClass;
-
- pFieldId = dexGetFieldId(pDvmDex->pDexFile, ifieldIdx);
-
- /*
- * Find the field's class.
- */
- resClass = dvmOptResolveClass(referrer, pFieldId->classIdx, pFailure);
- if (resClass == NULL) {
- //dvmClearOptException(dvmThreadSelf());
- assert(!dvmCheckException(dvmThreadSelf()));
- if (pFailure != NULL) { assert(!VERIFY_OK(*pFailure)); }
- return NULL;
- }
-
- resField = (InstField*)dvmFindFieldHier(resClass,
- dexStringById(pDvmDex->pDexFile, pFieldId->nameIdx),
- dexStringByTypeIdx(pDvmDex->pDexFile, pFieldId->typeIdx));
- if (resField == NULL) {
- LOGD("DexOpt: couldn't find field %s.%s\n",
- resClass->descriptor,
- dexStringById(pDvmDex->pDexFile, pFieldId->nameIdx));
- if (pFailure != NULL)
- *pFailure = VERIFY_ERROR_NO_FIELD;
- return NULL;
- }
- if (dvmIsStaticField(&resField->field)) {
- LOGD("DexOpt: wanted instance, got static for field %s.%s\n",
- resClass->descriptor,
- dexStringById(pDvmDex->pDexFile, pFieldId->nameIdx));
- if (pFailure != NULL)
- *pFailure = VERIFY_ERROR_CLASS_CHANGE;
- return NULL;
- }
-
- /*
- * Add it to the resolved table so we're faster on the next lookup.
- */
- dvmDexSetResolvedField(pDvmDex, ifieldIdx, (Field*) resField);
- }
-
- /* access allowed? */
- tweakLoader(referrer, resField->field.clazz);
- bool allowed = dvmCheckFieldAccess(referrer, (Field*)resField);
- untweakLoader(referrer, resField->field.clazz);
- if (!allowed) {
- LOGI("DexOpt: access denied from %s to field %s.%s\n",
- referrer->descriptor, resField->field.clazz->descriptor,
- resField->field.name);
- if (pFailure != NULL)
- *pFailure = VERIFY_ERROR_ACCESS_FIELD;
- return NULL;
- }
-
- return resField;
-}
-
-/*
- * Alternate version of dvmResolveStaticField().
- *
- * Does not force initialization of the resolved field's class.
- *
- * On failure, returns NULL, and sets *pFailure if pFailure is not NULL.
- */
-StaticField* dvmOptResolveStaticField(ClassObject* referrer, u4 sfieldIdx,
- VerifyError* pFailure)
-{
- DvmDex* pDvmDex = referrer->pDvmDex;
- StaticField* resField;
-
- resField = (StaticField*)dvmDexGetResolvedField(pDvmDex, sfieldIdx);
- if (resField == NULL) {
- const DexFieldId* pFieldId;
- ClassObject* resClass;
-
- pFieldId = dexGetFieldId(pDvmDex->pDexFile, sfieldIdx);
-
- /*
- * Find the field's class.
- */
- resClass = dvmOptResolveClass(referrer, pFieldId->classIdx, pFailure);
- if (resClass == NULL) {
- //dvmClearOptException(dvmThreadSelf());
- assert(!dvmCheckException(dvmThreadSelf()));
- if (pFailure != NULL) { assert(!VERIFY_OK(*pFailure)); }
- return NULL;
- }
-
- resField = (StaticField*)dvmFindFieldHier(resClass,
- dexStringById(pDvmDex->pDexFile, pFieldId->nameIdx),
- dexStringByTypeIdx(pDvmDex->pDexFile, pFieldId->typeIdx));
- if (resField == NULL) {
- LOGD("DexOpt: couldn't find static field\n");
- if (pFailure != NULL)
- *pFailure = VERIFY_ERROR_NO_FIELD;
- return NULL;
- }
- if (!dvmIsStaticField(&resField->field)) {
- LOGD("DexOpt: wanted static, got instance for field %s.%s\n",
- resClass->descriptor,
- dexStringById(pDvmDex->pDexFile, pFieldId->nameIdx));
- if (pFailure != NULL)
- *pFailure = VERIFY_ERROR_CLASS_CHANGE;
- return NULL;
- }
-
- /*
- * Add it to the resolved table so we're faster on the next lookup.
- *
- * We can only do this if we're in "dexopt", because the presence
- * of a valid value in the resolution table implies that the class
- * containing the static field has been initialized.
- */
- if (gDvm.optimizing)
- dvmDexSetResolvedField(pDvmDex, sfieldIdx, (Field*) resField);
- }
-
- /* access allowed? */
- tweakLoader(referrer, resField->field.clazz);
- bool allowed = dvmCheckFieldAccess(referrer, (Field*)resField);
- untweakLoader(referrer, resField->field.clazz);
- if (!allowed) {
- LOGI("DexOpt: access denied from %s to field %s.%s\n",
- referrer->descriptor, resField->field.clazz->descriptor,
- resField->field.name);
- if (pFailure != NULL)
- *pFailure = VERIFY_ERROR_ACCESS_FIELD;
- return NULL;
- }
-
- return resField;
-}
-
-
-/*
- * Rewrite an iget/iput instruction. These all have the form:
- * op vA, vB, field@CCCC
- *
- * Where vA holds the value, vB holds the object reference, and CCCC is
- * the field reference constant pool offset. We want to replace CCCC
- * with the byte offset from the start of the object.
- *
- * "clazz" is the referring class. We need this because we verify
- * access rights here.
- */
-static void rewriteInstField(Method* method, u2* insns, OpCode newOpc)
-{
- ClassObject* clazz = method->clazz;
- u2 fieldIdx = insns[1];
- InstField* field;
- int byteOffset;
-
- field = dvmOptResolveInstField(clazz, fieldIdx, NULL);
- if (field == NULL) {
- LOGI("DexOpt: unable to optimize field ref 0x%04x at 0x%02x in %s.%s\n",
- fieldIdx, (int) (insns - method->insns), clazz->descriptor,
- method->name);
- return;
- }
-
- if (field->byteOffset >= 65536) {
- LOGI("DexOpt: field offset exceeds 64K (%d)\n", field->byteOffset);
- return;
- }
-
- insns[0] = (insns[0] & 0xff00) | (u2) newOpc;
- insns[1] = (u2) field->byteOffset;
- LOGVV("DexOpt: rewrote access to %s.%s --> %d\n",
- field->field.clazz->descriptor, field->field.name,
- field->byteOffset);
-}
-
-/*
- * Alternate version of dvmResolveMethod().
- *
- * Doesn't throw exceptions, and checks access on every lookup.
- *
- * On failure, returns NULL, and sets *pFailure if pFailure is not NULL.
- */
-Method* dvmOptResolveMethod(ClassObject* referrer, u4 methodIdx,
- MethodType methodType, VerifyError* pFailure)
-{
- DvmDex* pDvmDex = referrer->pDvmDex;
- Method* resMethod;
-
- assert(methodType == METHOD_DIRECT ||
- methodType == METHOD_VIRTUAL ||
- methodType == METHOD_STATIC);
-
- LOGVV("--- resolving method %u (referrer=%s)\n", methodIdx,
- referrer->descriptor);
-
- resMethod = dvmDexGetResolvedMethod(pDvmDex, methodIdx);
- if (resMethod == NULL) {
- const DexMethodId* pMethodId;
- ClassObject* resClass;
-
- pMethodId = dexGetMethodId(pDvmDex->pDexFile, methodIdx);
-
- resClass = dvmOptResolveClass(referrer, pMethodId->classIdx, pFailure);
- if (resClass == NULL) {
- /*
- * Can't find the class that the method is a part of, or don't
- * have permission to access the class.
- */
- LOGV("DexOpt: can't find called method's class (?.%s)\n",
- dexStringById(pDvmDex->pDexFile, pMethodId->nameIdx));
- if (pFailure != NULL) { assert(!VERIFY_OK(*pFailure)); }
- return NULL;
- }
- if (dvmIsInterfaceClass(resClass)) {
- /* method is part of an interface; this is wrong method for that */
- LOGW("DexOpt: method is in an interface\n");
- if (pFailure != NULL)
- *pFailure = VERIFY_ERROR_GENERIC;
- return NULL;
- }
-
- /*
- * We need to chase up the class hierarchy to find methods defined
- * in super-classes. (We only want to check the current class
- * if we're looking for a constructor.)
- */
- DexProto proto;
- dexProtoSetFromMethodId(&proto, pDvmDex->pDexFile, pMethodId);
-
- if (methodType == METHOD_DIRECT) {
- resMethod = dvmFindDirectMethod(resClass,
- dexStringById(pDvmDex->pDexFile, pMethodId->nameIdx), &proto);
- } else {
- /* METHOD_STATIC or METHOD_VIRTUAL */
- resMethod = dvmFindMethodHier(resClass,
- dexStringById(pDvmDex->pDexFile, pMethodId->nameIdx), &proto);
- }
-
- if (resMethod == NULL) {
- LOGV("DexOpt: couldn't find method '%s'\n",
- dexStringById(pDvmDex->pDexFile, pMethodId->nameIdx));
- if (pFailure != NULL)
- *pFailure = VERIFY_ERROR_NO_METHOD;
- return NULL;
- }
- if (methodType == METHOD_STATIC) {
- if (!dvmIsStaticMethod(resMethod)) {
- LOGD("DexOpt: wanted static, got instance for method %s.%s\n",
- resClass->descriptor, resMethod->name);
- if (pFailure != NULL)
- *pFailure = VERIFY_ERROR_CLASS_CHANGE;
- return NULL;
- }
- } else if (methodType == METHOD_VIRTUAL) {
- if (dvmIsStaticMethod(resMethod)) {
- LOGD("DexOpt: wanted instance, got static for method %s.%s\n",
- resClass->descriptor, resMethod->name);
- if (pFailure != NULL)
- *pFailure = VERIFY_ERROR_CLASS_CHANGE;
- return NULL;
- }
- }
-
- /* see if this is a pure-abstract method */
- if (dvmIsAbstractMethod(resMethod) && !dvmIsAbstractClass(resClass)) {
- LOGW("DexOpt: pure-abstract method '%s' in %s\n",
- dexStringById(pDvmDex->pDexFile, pMethodId->nameIdx),
- resClass->descriptor);
- if (pFailure != NULL)
- *pFailure = VERIFY_ERROR_GENERIC;
- return NULL;
- }
-
- /*
- * Add it to the resolved table so we're faster on the next lookup.
- *
- * We can only do this for static methods if we're not in "dexopt",
- * because the presence of a valid value in the resolution table
- * implies that the class containing the static field has been
- * initialized.
- */
- if (methodType != METHOD_STATIC || gDvm.optimizing)
- dvmDexSetResolvedMethod(pDvmDex, methodIdx, resMethod);
- }
-
- LOGVV("--- found method %d (%s.%s)\n",
- methodIdx, resMethod->clazz->descriptor, resMethod->name);
-
- /* access allowed? */
- tweakLoader(referrer, resMethod->clazz);
- bool allowed = dvmCheckMethodAccess(referrer, resMethod);
- untweakLoader(referrer, resMethod->clazz);
- if (!allowed) {
- IF_LOGI() {
- char* desc = dexProtoCopyMethodDescriptor(&resMethod->prototype);
- LOGI("DexOpt: illegal method access (call %s.%s %s from %s)\n",
- resMethod->clazz->descriptor, resMethod->name, desc,
- referrer->descriptor);
- free(desc);
- }
- if (pFailure != NULL)
- *pFailure = VERIFY_ERROR_ACCESS_METHOD;
- return NULL;
- }
-
- return resMethod;
-}
-
-/*
- * Rewrite invoke-virtual, invoke-virtual/range, invoke-super, and
- * invoke-super/range. These all have the form:
- * op vAA, meth@BBBB, reg stuff @CCCC
- *
- * We want to replace the method constant pool index BBBB with the
- * vtable index.
- */
-static bool rewriteVirtualInvoke(Method* method, u2* insns, OpCode newOpc)
-{
- ClassObject* clazz = method->clazz;
- Method* baseMethod;
- u2 methodIdx = insns[1];
-
- baseMethod = dvmOptResolveMethod(clazz, methodIdx, METHOD_VIRTUAL, NULL);
- if (baseMethod == NULL) {
- LOGD("DexOpt: unable to optimize virt call 0x%04x at 0x%02x in %s.%s\n",
- methodIdx,
- (int) (insns - method->insns), clazz->descriptor,
- method->name);
- return false;
- }
-
- assert((insns[0] & 0xff) == OP_INVOKE_VIRTUAL ||
- (insns[0] & 0xff) == OP_INVOKE_VIRTUAL_RANGE ||
- (insns[0] & 0xff) == OP_INVOKE_SUPER ||
- (insns[0] & 0xff) == OP_INVOKE_SUPER_RANGE);
-
- /*
- * Note: Method->methodIndex is a u2 and is range checked during the
- * initial load.
- */
- insns[0] = (insns[0] & 0xff00) | (u2) newOpc;
- insns[1] = baseMethod->methodIndex;
-
- //LOGI("DexOpt: rewrote call to %s.%s --> %s.%s\n",
- // method->clazz->descriptor, method->name,
- // baseMethod->clazz->descriptor, baseMethod->name);
-
- return true;
-}
-
-/*
- * Rewrite invoke-direct, which has the form:
- * op vAA, meth@BBBB, reg stuff @CCCC
- *
- * There isn't a lot we can do to make this faster, but in some situations
- * we can make it go away entirely.
- *
- * This must only be used when the invoked method does nothing and has
- * no return value (the latter being very important for verification).
- */
-static bool rewriteEmptyDirectInvoke(Method* method, u2* insns)
-{
- ClassObject* clazz = method->clazz;
- Method* calledMethod;
- u2 methodIdx = insns[1];
-
- calledMethod = dvmOptResolveMethod(clazz, methodIdx, METHOD_DIRECT, NULL);
- if (calledMethod == NULL) {
- LOGD("DexOpt: unable to opt direct call 0x%04x at 0x%02x in %s.%s\n",
- methodIdx,
- (int) (insns - method->insns), clazz->descriptor,
- method->name);
- return false;
- }
-
- /* TODO: verify that java.lang.Object() is actually empty! */
- if (calledMethod->clazz == gDvm.classJavaLangObject &&
- dvmCompareNameDescriptorAndMethod("<init>", "()V", calledMethod) == 0)
- {
- /*
- * Replace with "empty" instruction. DO NOT disturb anything
- * else about it, as we want it to function the same as
- * OP_INVOKE_DIRECT when debugging is enabled.
- */
- assert((insns[0] & 0xff) == OP_INVOKE_DIRECT);
- insns[0] = (insns[0] & 0xff00) | (u2) OP_INVOKE_DIRECT_EMPTY;
-
- //LOGI("DexOpt: marked-empty call to %s.%s --> %s.%s\n",
- // method->clazz->descriptor, method->name,
- // calledMethod->clazz->descriptor, calledMethod->name);
- }
-
- return true;
-}
-
-/*
- * Resolve an interface method reference.
- *
- * No method access check here -- interface methods are always public.
- *
- * Returns NULL if the method was not found. Does not throw an exception.
- */
-Method* dvmOptResolveInterfaceMethod(ClassObject* referrer, u4 methodIdx)
-{
- DvmDex* pDvmDex = referrer->pDvmDex;
- Method* resMethod;
- int i;
-
- LOGVV("--- resolving interface method %d (referrer=%s)\n",
- methodIdx, referrer->descriptor);
-
- resMethod = dvmDexGetResolvedMethod(pDvmDex, methodIdx);
- if (resMethod == NULL) {
- const DexMethodId* pMethodId;
- ClassObject* resClass;
-
- pMethodId = dexGetMethodId(pDvmDex->pDexFile, methodIdx);
-
- resClass = dvmOptResolveClass(referrer, pMethodId->classIdx, NULL);
- if (resClass == NULL) {
- /* can't find the class that the method is a part of */
- dvmClearOptException(dvmThreadSelf());
- return NULL;
- }
- if (!dvmIsInterfaceClass(resClass)) {
- /* whoops */
- LOGI("Interface method not part of interface class\n");
- return NULL;
- }
-
- const char* methodName =
- dexStringById(pDvmDex->pDexFile, pMethodId->nameIdx);
- DexProto proto;
- dexProtoSetFromMethodId(&proto, pDvmDex->pDexFile, pMethodId);
-
- LOGVV("+++ looking for '%s' '%s' in resClass='%s'\n",
- methodName, methodSig, resClass->descriptor);
- resMethod = dvmFindVirtualMethod(resClass, methodName, &proto);
- if (resMethod == NULL) {
- /* scan superinterfaces and superclass interfaces */
- LOGVV("+++ did not resolve immediately\n");
- for (i = 0; i < resClass->iftableCount; i++) {
- resMethod = dvmFindVirtualMethod(resClass->iftable[i].clazz,
- methodName, &proto);
- if (resMethod != NULL)
- break;
- }
-
- if (resMethod == NULL) {
- LOGVV("+++ unable to resolve method %s\n", methodName);
- return NULL;
- }
- } else {
- LOGVV("+++ resolved immediately: %s (%s %d)\n", resMethod->name,
- resMethod->clazz->descriptor, (u4) resMethod->methodIndex);
- }
-
- /* we're expecting this to be abstract */
- if (!dvmIsAbstractMethod(resMethod)) {
- char* desc = dexProtoCopyMethodDescriptor(&resMethod->prototype);
- LOGW("Found non-abstract interface method %s.%s %s\n",
- resMethod->clazz->descriptor, resMethod->name, desc);
- free(desc);
- return NULL;
- }
-
- /*
- * Add it to the resolved table so we're faster on the next lookup.
- */
- dvmDexSetResolvedMethod(pDvmDex, methodIdx, resMethod);
- }
-
- LOGVV("--- found interface method %d (%s.%s)\n",
- methodIdx, resMethod->clazz->descriptor, resMethod->name);
-
- /* interface methods are always public; no need to check access */
-
- return resMethod;
-}
-
-/*
- * See if the method being called can be rewritten as an inline operation.
- * Works for invoke-virtual, invoke-direct, and invoke-static.
- *
- * Returns "true" if we replace it.
- */
-static bool rewriteExecuteInline(Method* method, u2* insns,
- MethodType methodType, const InlineSub* inlineSubs)
-{
- ClassObject* clazz = method->clazz;
- Method* calledMethod;
- u2 methodIdx = insns[1];
-
- //return false;
-
- calledMethod = dvmOptResolveMethod(clazz, methodIdx, methodType, NULL);
- if (calledMethod == NULL) {
- LOGV("+++ DexOpt inline: can't find %d\n", methodIdx);
- return false;
- }
-
- while (inlineSubs->method != NULL) {
- /*
- if (extra) {
- LOGI("comparing %p vs %p %s.%s %s\n",
- inlineSubs->method, calledMethod,
- inlineSubs->method->clazz->descriptor,
- inlineSubs->method->name,
- inlineSubs->method->signature);
- }
- */
- if (inlineSubs->method == calledMethod) {
- assert((insns[0] & 0xff) == OP_INVOKE_DIRECT ||
- (insns[0] & 0xff) == OP_INVOKE_STATIC ||
- (insns[0] & 0xff) == OP_INVOKE_VIRTUAL);
- insns[0] = (insns[0] & 0xff00) | (u2) OP_EXECUTE_INLINE;
- insns[1] = (u2) inlineSubs->inlineIdx;
-
- //LOGI("DexOpt: execute-inline %s.%s --> %s.%s\n",
- // method->clazz->descriptor, method->name,
- // calledMethod->clazz->descriptor, calledMethod->name);
- return true;
- }
-
- inlineSubs++;
- }
-
- return false;
-}
-
-/*
- * See if the method being called can be rewritten as an inline operation.
- * Works for invoke-virtual/range, invoke-direct/range, and invoke-static/range.
- *
- * Returns "true" if we replace it.
- */
-static bool rewriteExecuteInlineRange(Method* method, u2* insns,
- MethodType methodType, const InlineSub* inlineSubs)
-{
- ClassObject* clazz = method->clazz;
- Method* calledMethod;
- u2 methodIdx = insns[1];
-
- calledMethod = dvmOptResolveMethod(clazz, methodIdx, methodType, NULL);
- if (calledMethod == NULL) {
- LOGV("+++ DexOpt inline/range: can't find %d\n", methodIdx);
- return false;
- }
-
- while (inlineSubs->method != NULL) {
- if (inlineSubs->method == calledMethod) {
- assert((insns[0] & 0xff) == OP_INVOKE_DIRECT_RANGE ||
- (insns[0] & 0xff) == OP_INVOKE_STATIC_RANGE ||
- (insns[0] & 0xff) == OP_INVOKE_VIRTUAL_RANGE);
- insns[0] = (insns[0] & 0xff00) | (u2) OP_EXECUTE_INLINE_RANGE;
- insns[1] = (u2) inlineSubs->inlineIdx;
-
- //LOGI("DexOpt: execute-inline/range %s.%s --> %s.%s\n",
- // method->clazz->descriptor, method->name,
- // calledMethod->clazz->descriptor, calledMethod->name);
- return true;
- }
-
- inlineSubs++;
- }
-
- return false;
-}
-
diff --git a/vm/analysis/DexPrepare.c b/vm/analysis/DexPrepare.c
new file mode 100644
index 0000000..eea25c8
--- /dev/null
+++ b/vm/analysis/DexPrepare.c
@@ -0,0 +1,1207 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Prepare a DEX file for use by the VM. Depending upon the VM options
+ * we will attempt to verify and/or optimize the code, possibly appending
+ * register maps.
+ *
+ * TODO: the format of the optimized header is currently "whatever we
+ * happen to write", since the VM that writes it is by definition the same
+ * as the VM that reads it. Still, it should be better documented and
+ * more rigorously structured.
+ */
+#include "Dalvik.h"
+#include "libdex/OptInvocation.h"
+#include "analysis/RegisterMap.h"
+#include "analysis/Optimize.h"
+
+#include <zlib.h>
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+#include <errno.h>
+
+
+/* fwd */
+static void updateChecksum(u1* addr, int len, DexHeader* pHeader);
+static int writeDependencies(int fd, u4 modWhen, u4 crc);
+static bool writeAuxData(int fd, const DexClassLookup* pClassLookup,\
+ const IndexMapSet* pIndexMapSet, const RegisterMapBuilder* pRegMapBuilder);
+static void logFailedWrite(size_t expected, ssize_t actual, const char* msg,
+ int err);
+static bool computeFileChecksum(int fd, off_t start, size_t length, u4* pSum);
+
+
+/*
+ * Return the fd of an open file in the DEX file cache area. If the cache
+ * file doesn't exist or is out of date, this will remove the old entry,
+ * create a new one (writing only the file header), and return with the
+ * "new file" flag set.
+ *
+ * It's possible to execute from an unoptimized DEX file directly,
+ * assuming the byte ordering and structure alignment is correct, but
+ * disadvantageous because some significant optimizations are not possible.
+ * It's not generally possible to do the same from an uncompressed Jar
+ * file entry, because we have to guarantee 32-bit alignment in the
+ * memory-mapped file.
+ *
+ * For a Jar/APK file (a zip archive with "classes.dex" inside), "modWhen"
+ * and "crc32" come from the Zip directory entry. For a stand-alone DEX
+ * file, it's the modification date of the file and the Adler32 from the
+ * DEX header (which immediately follows the magic). If these don't
+ * match what's stored in the opt header, we reject the file immediately.
+ *
+ * On success, the file descriptor will be positioned just past the "opt"
+ * file header, and will be locked with flock. "*pCachedName" will point
+ * to newly-allocated storage.
+ */
+int dvmOpenCachedDexFile(const char* fileName, const char* cacheFileName,
+ u4 modWhen, u4 crc, bool isBootstrap, bool* pNewFile, bool createIfMissing)
+{
+ int fd, cc;
+ struct stat fdStat, fileStat;
+ bool readOnly = false;
+
+ *pNewFile = false;
+
+retry:
+ /*
+ * Try to open the cache file. If we've been asked to,
+ * create it if it doesn't exist.
+ */
+ fd = createIfMissing ? open(cacheFileName, O_CREAT|O_RDWR, 0644) : -1;
+ if (fd < 0) {
+ fd = open(cacheFileName, O_RDONLY, 0);
+ if (fd < 0) {
+ if (createIfMissing) {
+ LOGE("Can't open dex cache '%s': %s\n",
+ cacheFileName, strerror(errno));
+ }
+ return fd;
+ }
+ readOnly = true;
+ }
+
+ /*
+ * Grab an exclusive lock on the cache file. If somebody else is
+ * working on it, we'll block here until they complete. Because
+ * we're waiting on an external resource, we go into VMWAIT mode.
+ */
+ int oldStatus;
+ LOGV("DexOpt: locking cache file %s (fd=%d, boot=%d)\n",
+ cacheFileName, fd, isBootstrap);
+ oldStatus = dvmChangeStatus(NULL, THREAD_VMWAIT);
+ cc = flock(fd, LOCK_EX | LOCK_NB);
+ if (cc != 0) {
+ LOGD("DexOpt: sleeping on flock(%s)\n", cacheFileName);
+ cc = flock(fd, LOCK_EX);
+ }
+ dvmChangeStatus(NULL, oldStatus);
+ if (cc != 0) {
+ LOGE("Can't lock dex cache '%s': %d\n", cacheFileName, cc);
+ close(fd);
+ return -1;
+ }
+ LOGV("DexOpt: locked cache file\n");
+
+ /*
+ * Check to see if the fd we opened and locked matches the file in
+ * the filesystem. If they don't, then somebody else unlinked ours
+ * and created a new file, and we need to use that one instead. (If
+ * we caught them between the unlink and the create, we'll get an
+ * ENOENT from the file stat.)
+ */
+ cc = fstat(fd, &fdStat);
+ if (cc != 0) {
+ LOGE("Can't stat open file '%s'\n", cacheFileName);
+ LOGVV("DexOpt: unlocking cache file %s\n", cacheFileName);
+ goto close_fail;
+ }
+ cc = stat(cacheFileName, &fileStat);
+ if (cc != 0 ||
+ fdStat.st_dev != fileStat.st_dev || fdStat.st_ino != fileStat.st_ino)
+ {
+ LOGD("DexOpt: our open cache file is stale; sleeping and retrying\n");
+ LOGVV("DexOpt: unlocking cache file %s\n", cacheFileName);
+ flock(fd, LOCK_UN);
+ close(fd);
+ usleep(250 * 1000); /* if something is hosed, don't peg machine */
+ goto retry;
+ }
+
+ /*
+ * We have the correct file open and locked. If the file size is zero,
+ * then it was just created by us, and we want to fill in some fields
+ * in the "opt" header and set "*pNewFile". Otherwise, we want to
+ * verify that the fields in the header match our expectations, and
+ * reset the file if they don't.
+ */
+ if (fdStat.st_size == 0) {
+ if (readOnly) {
+ LOGW("DexOpt: file has zero length and isn't writable\n");
+ goto close_fail;
+ }
+ cc = dexOptCreateEmptyHeader(fd);
+ if (cc != 0)
+ goto close_fail;
+ *pNewFile = true;
+ LOGV("DexOpt: successfully initialized new cache file\n");
+ } else {
+ bool expectVerify, expectOpt;
+
+ if (gDvm.classVerifyMode == VERIFY_MODE_NONE)
+ expectVerify = false;
+ else if (gDvm.classVerifyMode == VERIFY_MODE_REMOTE)
+ expectVerify = !isBootstrap;
+ else /*if (gDvm.classVerifyMode == VERIFY_MODE_ALL)*/
+ expectVerify = true;
+
+ if (gDvm.dexOptMode == OPTIMIZE_MODE_NONE)
+ expectOpt = false;
+ else if (gDvm.dexOptMode == OPTIMIZE_MODE_VERIFIED)
+ expectOpt = expectVerify;
+ else /*if (gDvm.dexOptMode == OPTIMIZE_MODE_ALL)*/
+ expectOpt = true;
+
+ LOGV("checking deps, expecting vfy=%d opt=%d\n",
+ expectVerify, expectOpt);
+
+ if (!dvmCheckOptHeaderAndDependencies(fd, true, modWhen, crc,
+ expectVerify, expectOpt))
+ {
+ if (readOnly) {
+ /*
+ * We could unlink and rewrite the file if we own it or
+ * the "sticky" bit isn't set on the directory. However,
+ * we're not able to truncate it, which spoils things. So,
+ * give up now.
+ */
+ if (createIfMissing) {
+ LOGW("Cached DEX '%s' (%s) is stale and not writable\n",
+ fileName, cacheFileName);
+ }
+ goto close_fail;
+ }
+
+ /*
+ * If we truncate the existing file before unlinking it, any
+ * process that has it mapped will fail when it tries to touch
+ * the pages.
+ *
+ * This is very important. The zygote process will have the
+ * boot DEX files (core, framework, etc.) mapped early. If
+ * (say) core.dex gets updated, and somebody launches an app
+ * that uses App.dex, then App.dex gets reoptimized because it's
+ * dependent upon the boot classes. However, dexopt will be
+ * using the *new* core.dex to do the optimizations, while the
+ * app will actually be running against the *old* core.dex
+ * because it starts from zygote.
+ *
+ * Even without zygote, it's still possible for a class loader
+ * to pull in an APK that was optimized against an older set
+ * of DEX files. We must ensure that everything fails when a
+ * boot DEX gets updated, and for general "why aren't my
+ * changes doing anything" purposes its best if we just make
+ * everything crash when a DEX they're using gets updated.
+ */
+ LOGD("Stale deps in cache file; removing and retrying\n");
+ if (ftruncate(fd, 0) != 0) {
+ LOGW("Warning: unable to truncate cache file '%s': %s\n",
+ cacheFileName, strerror(errno));
+ /* keep going */
+ }
+ if (unlink(cacheFileName) != 0) {
+ LOGW("Warning: unable to remove cache file '%s': %d %s\n",
+ cacheFileName, errno, strerror(errno));
+ /* keep going; permission failure should probably be fatal */
+ }
+ LOGVV("DexOpt: unlocking cache file %s\n", cacheFileName);
+ flock(fd, LOCK_UN);
+ close(fd);
+ goto retry;
+ } else {
+ LOGV("DexOpt: good deps in cache file\n");
+ }
+ }
+
+ assert(fd >= 0);
+ return fd;
+
+close_fail:
+ flock(fd, LOCK_UN);
+ close(fd);
+ return -1;
+}
+
+/*
+ * Unlock the file descriptor.
+ *
+ * Returns "true" on success.
+ */
+bool dvmUnlockCachedDexFile(int fd)
+{
+ LOGVV("DexOpt: unlocking cache file fd=%d\n", fd);
+ return (flock(fd, LOCK_UN) == 0);
+}
+
+
+/*
+ * Given a descriptor for a file with DEX data in it, produce an
+ * optimized version.
+ *
+ * The file pointed to by "fd" is expected to be a locked shared resource
+ * (or private); we make no efforts to enforce multi-process correctness
+ * here.
+ *
+ * "fileName" is only used for debug output. "modWhen" and "crc" are stored
+ * in the dependency set.
+ *
+ * The "isBootstrap" flag determines how the optimizer and verifier handle
+ * package-scope access checks. When optimizing, we only load the bootstrap
+ * class DEX files and the target DEX, so the flag determines whether the
+ * target DEX classes are given a (synthetic) non-NULL classLoader pointer.
+ * This only really matters if the target DEX contains classes that claim to
+ * be in the same package as bootstrap classes.
+ *
+ * The optimizer will need to load every class in the target DEX file.
+ * This is generally undesirable, so we start a subprocess to do the
+ * work and wait for it to complete.
+ *
+ * Returns "true" on success. All data will have been written to "fd".
+ */
+bool dvmOptimizeDexFile(int fd, off_t dexOffset, long dexLength,
+ const char* fileName, u4 modWhen, u4 crc, bool isBootstrap)
+{
+ const char* lastPart = strrchr(fileName, '/');
+ if (lastPart != NULL)
+ lastPart++;
+ else
+ lastPart = fileName;
+
+ /*
+ * For basic optimizations (byte-swapping and structure aligning) we
+ * don't need to fork(). It looks like fork+exec is causing problems
+ * with gdb on our bewildered Linux distro, so in some situations we
+ * want to avoid this.
+ *
+ * For optimization and/or verification, we need to load all the classes.
+ *
+ * We don't check gDvm.generateRegisterMaps, since that is dependent
+ * upon the verifier state.
+ */
+ if (gDvm.classVerifyMode == VERIFY_MODE_NONE &&
+ (gDvm.dexOptMode == OPTIMIZE_MODE_NONE ||
+ gDvm.dexOptMode == OPTIMIZE_MODE_VERIFIED))
+ {
+ LOGD("DexOpt: --- BEGIN (quick) '%s' ---\n", lastPart);
+ return dvmContinueOptimization(fd, dexOffset, dexLength,
+ fileName, modWhen, crc, isBootstrap);
+ }
+
+
+ LOGD("DexOpt: --- BEGIN '%s' (bootstrap=%d) ---\n", lastPart, isBootstrap);
+
+ pid_t pid;
+
+ /*
+ * This could happen if something in our bootclasspath, which we thought
+ * was all optimized, got rejected.
+ */
+ if (gDvm.optimizing) {
+ LOGW("Rejecting recursive optimization attempt on '%s'\n", fileName);
+ return false;
+ }
+
+ pid = fork();
+ if (pid == 0) {
+ static const int kUseValgrind = 0;
+ static const char* kDexOptBin = "/bin/dexopt";
+ static const char* kValgrinder = "/usr/bin/valgrind";
+ static const int kFixedArgCount = 10;
+ static const int kValgrindArgCount = 5;
+ static const int kMaxIntLen = 12; // '-'+10dig+'\0' -OR- 0x+8dig
+ int bcpSize = dvmGetBootPathSize();
+ int argc = kFixedArgCount + bcpSize
+ + (kValgrindArgCount * kUseValgrind);
+ char* argv[argc+1]; // last entry is NULL
+ char values[argc][kMaxIntLen];
+ char* execFile;
+ char* androidRoot;
+ int flags;
+
+ /* change process groups, so we don't clash with ProcessManager */
+ setpgid(0, 0);
+
+ /* full path to optimizer */
+ androidRoot = getenv("ANDROID_ROOT");
+ if (androidRoot == NULL) {
+ LOGW("ANDROID_ROOT not set, defaulting to /system\n");
+ androidRoot = "/system";
+ }
+ execFile = malloc(strlen(androidRoot) + strlen(kDexOptBin) + 1);
+ strcpy(execFile, androidRoot);
+ strcat(execFile, kDexOptBin);
+
+ /*
+ * Create arg vector.
+ */
+ int curArg = 0;
+
+ if (kUseValgrind) {
+ /* probably shouldn't ship the hard-coded path */
+ argv[curArg++] = (char*)kValgrinder;
+ argv[curArg++] = "--tool=memcheck";
+ argv[curArg++] = "--leak-check=yes"; // check for leaks too
+ argv[curArg++] = "--leak-resolution=med"; // increase from 2 to 4
+ argv[curArg++] = "--num-callers=16"; // default is 12
+ assert(curArg == kValgrindArgCount);
+ }
+ argv[curArg++] = execFile;
+
+ argv[curArg++] = "--dex";
+
+ sprintf(values[2], "%d", DALVIK_VM_BUILD);
+ argv[curArg++] = values[2];
+
+ sprintf(values[3], "%d", fd);
+ argv[curArg++] = values[3];
+
+ sprintf(values[4], "%d", (int) dexOffset);
+ argv[curArg++] = values[4];
+
+ sprintf(values[5], "%d", (int) dexLength);
+ argv[curArg++] = values[5];
+
+ argv[curArg++] = (char*)fileName;
+
+ sprintf(values[7], "%d", (int) modWhen);
+ argv[curArg++] = values[7];
+
+ sprintf(values[8], "%d", (int) crc);
+ argv[curArg++] = values[8];
+
+ flags = 0;
+ if (gDvm.dexOptMode != OPTIMIZE_MODE_NONE) {
+ flags |= DEXOPT_OPT_ENABLED;
+ if (gDvm.dexOptMode == OPTIMIZE_MODE_ALL)
+ flags |= DEXOPT_OPT_ALL;
+ }
+ if (gDvm.classVerifyMode != VERIFY_MODE_NONE) {
+ flags |= DEXOPT_VERIFY_ENABLED;
+ if (gDvm.classVerifyMode == VERIFY_MODE_ALL)
+ flags |= DEXOPT_VERIFY_ALL;
+ }
+ if (isBootstrap)
+ flags |= DEXOPT_IS_BOOTSTRAP;
+ if (gDvm.generateRegisterMaps)
+ flags |= DEXOPT_GEN_REGISTER_MAP;
+ sprintf(values[9], "%d", flags);
+ argv[curArg++] = values[9];
+
+ assert(((!kUseValgrind && curArg == kFixedArgCount) ||
+ ((kUseValgrind && curArg == kFixedArgCount+kValgrindArgCount))));
+
+ ClassPathEntry* cpe;
+ for (cpe = gDvm.bootClassPath; cpe->ptr != NULL; cpe++) {
+ argv[curArg++] = cpe->fileName;
+ }
+ assert(curArg == argc);
+
+ argv[curArg] = NULL;
+
+ if (kUseValgrind)
+ execv(kValgrinder, argv);
+ else
+ execv(execFile, argv);
+
+ LOGE("execv '%s'%s failed: %s\n", execFile,
+ kUseValgrind ? " [valgrind]" : "", strerror(errno));
+ exit(1);
+ } else {
+ LOGV("DexOpt: waiting for verify+opt, pid=%d\n", (int) pid);
+ int status;
+ pid_t gotPid;
+ int oldStatus;
+
+ /*
+ * Wait for the optimization process to finish. We go into VMWAIT
+ * mode here so GC suspension won't have to wait for us.
+ */
+ oldStatus = dvmChangeStatus(NULL, THREAD_VMWAIT);
+ while (true) {
+ gotPid = waitpid(pid, &status, 0);
+ if (gotPid == -1 && errno == EINTR) {
+ LOGD("waitpid interrupted, retrying\n");
+ } else {
+ break;
+ }
+ }
+ dvmChangeStatus(NULL, oldStatus);
+ if (gotPid != pid) {
+ LOGE("waitpid failed: wanted %d, got %d: %s\n",
+ (int) pid, (int) gotPid, strerror(errno));
+ return false;
+ }
+
+ if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
+ LOGD("DexOpt: --- END '%s' (success) ---\n", lastPart);
+ return true;
+ } else {
+ LOGW("DexOpt: --- END '%s' --- status=0x%04x, process failed\n",
+ lastPart, status);
+ return false;
+ }
+ }
+}
+
+/*
+ * Do the actual optimization. This is called directly for "minimal"
+ * optimization, or from a newly-created process for "full" optimization.
+ *
+ * For best use of disk/memory, we want to extract once and perform
+ * optimizations in place. If the file has to expand or contract
+ * to match local structure padding/alignment expectations, we want
+ * to do the rewrite as part of the extract, rather than extracting
+ * into a temp file and slurping it back out. (The structure alignment
+ * is currently correct for all platforms, and this isn't expected to
+ * change, so we should be okay with having it already extracted.)
+ *
+ * Returns "true" on success.
+ */
+bool dvmContinueOptimization(int fd, off_t dexOffset, long dexLength,
+ const char* fileName, u4 modWhen, u4 crc, bool isBootstrap)
+{
+ DexClassLookup* pClassLookup = NULL;
+ IndexMapSet* pIndexMapSet = NULL;
+ RegisterMapBuilder* pRegMapBuilder = NULL;
+ bool doVerify, doOpt;
+ u4 headerFlags = 0;
+
+ if (gDvm.classVerifyMode == VERIFY_MODE_NONE)
+ doVerify = false;
+ else if (gDvm.classVerifyMode == VERIFY_MODE_REMOTE)
+ doVerify = !isBootstrap;
+ else /*if (gDvm.classVerifyMode == VERIFY_MODE_ALL)*/
+ doVerify = true;
+
+ if (gDvm.dexOptMode == OPTIMIZE_MODE_NONE)
+ doOpt = false;
+ else if (gDvm.dexOptMode == OPTIMIZE_MODE_VERIFIED)
+ doOpt = doVerify;
+ else /*if (gDvm.dexOptMode == OPTIMIZE_MODE_ALL)*/
+ doOpt = true;
+
+ LOGV("Continuing optimization (%s, isb=%d, vfy=%d, opt=%d)\n",
+ fileName, isBootstrap, doVerify, doOpt);
+
+ assert(dexOffset >= 0);
+
+ /* quick test so we don't blow up on empty file */
+ if (dexLength < (int) sizeof(DexHeader)) {
+ LOGE("too small to be DEX\n");
+ return false;
+ }
+ if (dexOffset < (int) sizeof(DexOptHeader)) {
+ LOGE("not enough room for opt header\n");
+ return false;
+ }
+
+ bool result = false;
+
+ /*
+ * Drop this into a global so we don't have to pass it around. We could
+ * also add a field to DexFile, but since it only pertains to DEX
+ * creation that probably doesn't make sense.
+ */
+ gDvm.optimizingBootstrapClass = isBootstrap;
+
+ {
+ /*
+ * Map the entire file (so we don't have to worry about page
+ * alignment). The expectation is that the output file contains
+ * our DEX data plus room for a small header.
+ */
+ bool success;
+ void* mapAddr;
+ mapAddr = mmap(NULL, dexOffset + dexLength, PROT_READ|PROT_WRITE,
+ MAP_SHARED, fd, 0);
+ if (mapAddr == MAP_FAILED) {
+ LOGE("unable to mmap DEX cache: %s\n", strerror(errno));
+ goto bail;
+ }
+
+ /*
+ * Rewrite the file. Byte reordering, structure realigning,
+ * class verification, and bytecode optimization are all performed
+ * here.
+ *
+ * In theory the file could change size and bits could shift around.
+ * In practice this would be annoying to deal with, so the file
+ * layout is designed so that it can always be rewritten in place.
+ *
+ * This sets "headerFlags" and creates the class lookup table as
+ * part of doing the processing.
+ */
+ success = dvmRewriteDex(((u1*) mapAddr) + dexOffset, dexLength,
+ doVerify, doOpt, &headerFlags, &pClassLookup);
+
+ if (success) {
+ DvmDex* pDvmDex = NULL;
+ u1* dexAddr = ((u1*) mapAddr) + dexOffset;
+
+ if (dvmDexFileOpenPartial(dexAddr, dexLength, &pDvmDex) != 0) {
+ LOGE("Unable to create DexFile\n");
+ success = false;
+ } else {
+ /*
+ * If configured to do so, scan the instructions, looking
+ * for ways to reduce the size of the resolved-constant table.
+ * This is done post-optimization, across the instructions
+ * in all methods in all classes (even the ones that failed
+ * to load).
+ */
+ pIndexMapSet = dvmRewriteConstants(pDvmDex);
+
+ /*
+ * If configured to do so, generate a full set of register
+ * maps for all verified classes.
+ */
+ if (gDvm.generateRegisterMaps) {
+ pRegMapBuilder = dvmGenerateRegisterMaps(pDvmDex);
+ if (pRegMapBuilder == NULL) {
+ LOGE("Failed generating register maps\n");
+ success = false;
+ }
+ }
+
+ DexHeader* pHeader = (DexHeader*)pDvmDex->pHeader;
+ updateChecksum(dexAddr, dexLength, pHeader);
+
+ dvmDexFileFree(pDvmDex);
+ }
+ }
+
+ /* unmap the read-write version, forcing writes to disk */
+ if (msync(mapAddr, dexOffset + dexLength, MS_SYNC) != 0) {
+ LOGW("msync failed: %s\n", strerror(errno));
+ // weird, but keep going
+ }
+#if 1
+ /*
+ * This causes clean shutdown to fail, because we have loaded classes
+ * that point into it. For the optimizer this isn't a problem,
+ * because it's more efficient for the process to simply exit.
+ * Exclude this code when doing clean shutdown for valgrind.
+ */
+ if (munmap(mapAddr, dexOffset + dexLength) != 0) {
+ LOGE("munmap failed: %s\n", strerror(errno));
+ goto bail;
+ }
+#endif
+
+ if (!success)
+ goto bail;
+ }
+
+ /* get start offset, and adjust deps start for 64-bit alignment */
+ off_t depsOffset, auxOffset, endOffset, adjOffset;
+ int depsLength, auxLength;
+ u4 optChecksum;
+
+ depsOffset = lseek(fd, 0, SEEK_END);
+ if (depsOffset < 0) {
+ LOGE("lseek to EOF failed: %s\n", strerror(errno));
+ goto bail;
+ }
+ adjOffset = (depsOffset + 7) & ~(0x07);
+ if (adjOffset != depsOffset) {
+ LOGV("Adjusting deps start from %d to %d\n",
+ (int) depsOffset, (int) adjOffset);
+ depsOffset = adjOffset;
+ lseek(fd, depsOffset, SEEK_SET);
+ }
+
+ /*
+ * Append the dependency list.
+ */
+ if (writeDependencies(fd, modWhen, crc) != 0) {
+ LOGW("Failed writing dependencies\n");
+ goto bail;
+ }
+
+ /* compute deps length, then adjust aux start for 64-bit alignment */
+ auxOffset = lseek(fd, 0, SEEK_END);
+ depsLength = auxOffset - depsOffset;
+
+ adjOffset = (auxOffset + 7) & ~(0x07);
+ if (adjOffset != auxOffset) {
+ LOGV("Adjusting aux start from %d to %d\n",
+ (int) auxOffset, (int) adjOffset);
+ auxOffset = adjOffset;
+ lseek(fd, auxOffset, SEEK_SET);
+ }
+
+ /*
+ * Append any auxillary pre-computed data structures.
+ */
+ if (!writeAuxData(fd, pClassLookup, pIndexMapSet, pRegMapBuilder)) {
+ LOGW("Failed writing aux data\n");
+ goto bail;
+ }
+
+ endOffset = lseek(fd, 0, SEEK_END);
+ auxLength = endOffset - auxOffset;
+
+ /* compute checksum from start of deps to end of aux area */
+ if (!computeFileChecksum(fd, depsOffset,
+ (auxOffset+auxLength) - depsOffset, &optChecksum))
+ {
+ goto bail;
+ }
+
+ /*
+ * Output the "opt" header with all values filled in and a correct
+ * magic number.
+ */
+ DexOptHeader optHdr;
+ memset(&optHdr, 0xff, sizeof(optHdr));
+ memcpy(optHdr.magic, DEX_OPT_MAGIC, 4);
+ memcpy(optHdr.magic+4, DEX_OPT_MAGIC_VERS, 4);
+ optHdr.dexOffset = (u4) dexOffset;
+ optHdr.dexLength = (u4) dexLength;
+ optHdr.depsOffset = (u4) depsOffset;
+ optHdr.depsLength = (u4) depsLength;
+ optHdr.auxOffset = (u4) auxOffset;
+ optHdr.auxLength = (u4) auxLength;
+
+ optHdr.flags = headerFlags;
+ optHdr.checksum = optChecksum;
+
+ ssize_t actual;
+ lseek(fd, 0, SEEK_SET);
+ actual = write(fd, &optHdr, sizeof(optHdr));
+ if (actual != sizeof(optHdr)) {
+ logFailedWrite(sizeof(optHdr), actual, "opt header", errno);
+ goto bail;
+ }
+
+ LOGV("Successfully wrote DEX header\n");
+ result = true;
+
+ //dvmRegisterMapDumpStats();
+
+bail:
+ dvmFreeIndexMapSet(pIndexMapSet);
+ dvmFreeRegisterMapBuilder(pRegMapBuilder);
+ free(pClassLookup);
+ return result;
+}
+
+
+/*
+ * Get the cache file name from a ClassPathEntry.
+ */
+static const char* getCacheFileName(const ClassPathEntry* cpe)
+{
+ switch (cpe->kind) {
+ case kCpeJar:
+ return dvmGetJarFileCacheFileName((JarFile*) cpe->ptr);
+ case kCpeDex:
+ return dvmGetRawDexFileCacheFileName((RawDexFile*) cpe->ptr);
+ default:
+ LOGE("DexOpt: unexpected cpe kind %d\n", cpe->kind);
+ dvmAbort();
+ return NULL;
+ }
+}
+
+/*
+ * Get the SHA-1 signature.
+ */
+static const u1* getSignature(const ClassPathEntry* cpe)
+{
+ DvmDex* pDvmDex;
+
+ switch (cpe->kind) {
+ case kCpeJar:
+ pDvmDex = dvmGetJarFileDex((JarFile*) cpe->ptr);
+ break;
+ case kCpeDex:
+ pDvmDex = dvmGetRawDexFileDex((RawDexFile*) cpe->ptr);
+ break;
+ default:
+ LOGE("unexpected cpe kind %d\n", cpe->kind);
+ dvmAbort();
+ pDvmDex = NULL; // make gcc happy
+ }
+
+ assert(pDvmDex != NULL);
+ return pDvmDex->pDexFile->pHeader->signature;
+}
+
+
+/*
+ * Dependency layout:
+ * 4b Source file modification time, in seconds since 1970 UTC
+ * 4b CRC-32 from Zip entry, or Adler32 from source DEX header
+ * 4b Dalvik VM build number
+ * 4b Number of dependency entries that follow
+ * Dependency entries:
+ * 4b Name length (including terminating null)
+ * var Full path of cache entry (null terminated)
+ * 20b SHA-1 signature from source DEX file
+ *
+ * If this changes, update DEX_OPT_MAGIC_VERS.
+ */
+static const size_t kMinDepSize = 4 * 4;
+static const size_t kMaxDepSize = 4 * 4 + 1024; // sanity check
+
+/*
+ * Read the "opt" header, verify it, then read the dependencies section
+ * and verify that data as well.
+ *
+ * If "sourceAvail" is "true", this will verify that "modWhen" and "crc"
+ * match up with what is stored in the header. If they don't, we reject
+ * the file so that it can be recreated from the updated original. If
+ * "sourceAvail" isn't set, e.g. for a .odex file, we ignore these arguments.
+ *
+ * On successful return, the file will be seeked immediately past the
+ * "opt" header.
+ */
+bool dvmCheckOptHeaderAndDependencies(int fd, bool sourceAvail, u4 modWhen,
+ u4 crc, bool expectVerify, bool expectOpt)
+{
+ DexOptHeader optHdr;
+ u1* depData = NULL;
+ const u1* magic;
+ off_t posn;
+ int result = false;
+ ssize_t actual;
+
+ /*
+ * Start at the start. The "opt" header, when present, will always be
+ * the first thing in the file.
+ */
+ if (lseek(fd, 0, SEEK_SET) != 0) {
+ LOGE("DexOpt: failed to seek to start of file: %s\n", strerror(errno));
+ goto bail;
+ }
+
+ /*
+ * Read and do trivial verification on the opt header. The header is
+ * always in host byte order.
+ */
+ if (read(fd, &optHdr, sizeof(optHdr)) != sizeof(optHdr)) {
+ LOGE("DexOpt: failed reading opt header: %s\n", strerror(errno));
+ goto bail;
+ }
+
+ magic = optHdr.magic;
+ if (memcmp(magic, DEX_OPT_MAGIC, 4) != 0) {
+ /* not a DEX file, or previous attempt was interrupted */
+ LOGD("DexOpt: incorrect opt magic number (0x%02x %02x %02x %02x)\n",
+ magic[0], magic[1], magic[2], magic[3]);
+ goto bail;
+ }
+ if (memcmp(magic+4, DEX_OPT_MAGIC_VERS, 4) != 0) {
+ LOGW("DexOpt: stale opt version (0x%02x %02x %02x %02x)\n",
+ magic[4], magic[5], magic[6], magic[7]);
+ goto bail;
+ }
+ if (optHdr.depsLength < kMinDepSize || optHdr.depsLength > kMaxDepSize) {
+ LOGW("DexOpt: weird deps length %d, bailing\n", optHdr.depsLength);
+ goto bail;
+ }
+
+ /*
+ * Do the header flags match up with what we want?
+ *
+ * This is useful because it allows us to automatically regenerate
+ * a file when settings change (e.g. verification is now mandatory),
+ * but can cause difficulties if the bootstrap classes we depend upon
+ * were handled differently than the current options specify. We get
+ * upset because they're not verified or optimized, but we're not able
+ * to regenerate them because the installer won't let us.
+ *
+ * (This is also of limited value when !sourceAvail.)
+ *
+ * So, for now, we essentially ignore "expectVerify" and "expectOpt"
+ * by limiting the match mask.
+ *
+ * The only thing we really can't handle is incorrect byte-ordering.
+ */
+ const u4 matchMask = DEX_OPT_FLAG_BIG;
+ u4 expectedFlags = 0;
+#if __BYTE_ORDER != __LITTLE_ENDIAN
+ expectedFlags |= DEX_OPT_FLAG_BIG;
+#endif
+ if (expectVerify)
+ expectedFlags |= DEX_FLAG_VERIFIED;
+ if (expectOpt)
+ expectedFlags |= DEX_OPT_FLAG_FIELDS | DEX_OPT_FLAG_INVOCATIONS;
+ if ((expectedFlags & matchMask) != (optHdr.flags & matchMask)) {
+ LOGI("DexOpt: header flag mismatch (0x%02x vs 0x%02x, mask=0x%02x)\n",
+ expectedFlags, optHdr.flags, matchMask);
+ goto bail;
+ }
+
+ posn = lseek(fd, optHdr.depsOffset, SEEK_SET);
+ if (posn < 0) {
+ LOGW("DexOpt: seek to deps failed: %s\n", strerror(errno));
+ goto bail;
+ }
+
+ /*
+ * Read all of the dependency stuff into memory.
+ */
+ depData = (u1*) malloc(optHdr.depsLength);
+ if (depData == NULL) {
+ LOGW("DexOpt: unable to allocate %d bytes for deps\n",
+ optHdr.depsLength);
+ goto bail;
+ }
+ actual = read(fd, depData, optHdr.depsLength);
+ if (actual != (ssize_t) optHdr.depsLength) {
+ LOGW("DexOpt: failed reading deps: %d of %d (err=%s)\n",
+ (int) actual, optHdr.depsLength, strerror(errno));
+ goto bail;
+ }
+
+ /*
+ * Verify simple items.
+ */
+ const u1* ptr;
+ u4 val;
+
+ ptr = depData;
+ val = read4LE(&ptr);
+ if (sourceAvail && val != modWhen) {
+ LOGI("DexOpt: source file mod time mismatch (%08x vs %08x)\n",
+ val, modWhen);
+ goto bail;
+ }
+ val = read4LE(&ptr);
+ if (sourceAvail && val != crc) {
+ LOGI("DexOpt: source file CRC mismatch (%08x vs %08x)\n", val, crc);
+ goto bail;
+ }
+ val = read4LE(&ptr);
+ if (val != DALVIK_VM_BUILD) {
+ LOGD("DexOpt: VM build version mismatch (%d vs %d)\n",
+ val, DALVIK_VM_BUILD);
+ goto bail;
+ }
+
+ /*
+ * Verify dependencies on other cached DEX files. It must match
+ * exactly with what is currently defined in the bootclasspath.
+ */
+ ClassPathEntry* cpe;
+ u4 numDeps;
+
+ numDeps = read4LE(&ptr);
+ LOGV("+++ DexOpt: numDeps = %d\n", numDeps);
+ for (cpe = gDvm.bootClassPath; cpe->ptr != NULL; cpe++) {
+ const char* cacheFileName = getCacheFileName(cpe);
+ const u1* signature = getSignature(cpe);
+ size_t len = strlen(cacheFileName) +1;
+ u4 storedStrLen;
+
+ if (numDeps == 0) {
+ /* more entries in bootclasspath than in deps list */
+ LOGI("DexOpt: not all deps represented\n");
+ goto bail;
+ }
+
+ storedStrLen = read4LE(&ptr);
+ if (len != storedStrLen ||
+ strcmp(cacheFileName, (const char*) ptr) != 0)
+ {
+ LOGI("DexOpt: mismatch dep name: '%s' vs. '%s'\n",
+ cacheFileName, ptr);
+ goto bail;
+ }
+
+ ptr += storedStrLen;
+
+ if (memcmp(signature, ptr, kSHA1DigestLen) != 0) {
+ LOGI("DexOpt: mismatch dep signature for '%s'\n", cacheFileName);
+ goto bail;
+ }
+ ptr += kSHA1DigestLen;
+
+ LOGV("DexOpt: dep match on '%s'\n", cacheFileName);
+
+ numDeps--;
+ }
+
+ if (numDeps != 0) {
+ /* more entries in deps list than in classpath */
+ LOGI("DexOpt: Some deps went away\n");
+ goto bail;
+ }
+
+ // consumed all data and no more?
+ if (ptr != depData + optHdr.depsLength) {
+ LOGW("DexOpt: Spurious dep data? %d vs %d\n",
+ (int) (ptr - depData), optHdr.depsLength);
+ assert(false);
+ }
+
+ result = true;
+
+bail:
+ free(depData);
+ return result;
+}
+
+/*
+ * Write the dependency info to "fd" at the current file position.
+ */
+static int writeDependencies(int fd, u4 modWhen, u4 crc)
+{
+ u1* buf = NULL;
+ ssize_t actual;
+ int result = -1;
+ ssize_t bufLen;
+ ClassPathEntry* cpe;
+ int i, numDeps;
+
+ /*
+ * Count up the number of completed entries in the bootclasspath.
+ */
+ numDeps = 0;
+ bufLen = 0;
+ for (cpe = gDvm.bootClassPath; cpe->ptr != NULL; cpe++) {
+ const char* cacheFileName = getCacheFileName(cpe);
+ LOGV("+++ DexOpt: found dep '%s'\n", cacheFileName);
+
+ numDeps++;
+ bufLen += strlen(cacheFileName) +1;
+ }
+
+ bufLen += 4*4 + numDeps * (4+kSHA1DigestLen);
+
+ buf = malloc(bufLen);
+
+ set4LE(buf+0, modWhen);
+ set4LE(buf+4, crc);
+ set4LE(buf+8, DALVIK_VM_BUILD);
+ set4LE(buf+12, numDeps);
+
+ // TODO: do we want to add dvmGetInlineOpsTableLength() here? Won't
+ // help us if somebody replaces an existing entry, but it'd catch
+ // additions/removals.
+
+ u1* ptr = buf + 4*4;
+ for (cpe = gDvm.bootClassPath; cpe->ptr != NULL; cpe++) {
+ const char* cacheFileName = getCacheFileName(cpe);
+ const u1* signature = getSignature(cpe);
+ int len = strlen(cacheFileName) +1;
+
+ if (ptr + 4 + len + kSHA1DigestLen > buf + bufLen) {
+ LOGE("DexOpt: overran buffer\n");
+ dvmAbort();
+ }
+
+ set4LE(ptr, len);
+ ptr += 4;
+ memcpy(ptr, cacheFileName, len);
+ ptr += len;
+ memcpy(ptr, signature, kSHA1DigestLen);
+ ptr += kSHA1DigestLen;
+ }
+
+ assert(ptr == buf + bufLen);
+
+ actual = write(fd, buf, bufLen);
+ if (actual != bufLen) {
+ result = (errno != 0) ? errno : -1;
+ logFailedWrite(bufLen, actual, "dep info", errno);
+ } else {
+ result = 0;
+ }
+
+ free(buf);
+ return result;
+}
+
+
+/*
+ * Write a block of data in "chunk" format.
+ *
+ * The chunk header fields are always in "native" byte order. If "size"
+ * is not a multiple of 8 bytes, the data area is padded out.
+ */
+static bool writeChunk(int fd, u4 type, const void* data, size_t size)
+{
+ ssize_t actual;
+ union { /* save a syscall by grouping these together */
+ char raw[8];
+ struct {
+ u4 type;
+ u4 size;
+ } ts;
+ } header;
+
+ assert(sizeof(header) == 8);
+
+ LOGV("Writing chunk, type=%.4s size=%d\n", (char*) &type, size);
+
+ header.ts.type = type;
+ header.ts.size = (u4) size;
+ actual = write(fd, &header, sizeof(header));
+ if (actual != sizeof(header)) {
+ logFailedWrite(size, actual, "aux chunk header write", errno);
+ return false;
+ }
+
+ if (size > 0) {
+ actual = write(fd, data, size);
+ if (actual != (ssize_t) size) {
+ logFailedWrite(size, actual, "aux chunk write", errno);
+ return false;
+ }
+ }
+
+ /* if necessary, pad to 64-bit alignment */
+ if ((size & 7) != 0) {
+ int padSize = 8 - (size & 7);
+ LOGV("size was %d, inserting %d pad bytes\n", size, padSize);
+ lseek(fd, padSize, SEEK_CUR);
+ }
+
+ assert( ((int)lseek(fd, 0, SEEK_CUR) & 7) == 0);
+
+ return true;
+}
+
+/*
+ * Write aux data.
+ *
+ * We have different pieces, some of which may be optional. To make the
+ * most effective use of space, we use a "chunk" format, with a 4-byte
+ * type and a 4-byte length. We guarantee 64-bit alignment for the data,
+ * so it can be used directly when the file is mapped for reading.
+ */
+static bool writeAuxData(int fd, const DexClassLookup* pClassLookup,
+ const IndexMapSet* pIndexMapSet, const RegisterMapBuilder* pRegMapBuilder)
+{
+ /* pre-computed class lookup hash table */
+ if (!writeChunk(fd, (u4) kDexChunkClassLookup,
+ pClassLookup, pClassLookup->size))
+ {
+ return false;
+ }
+
+ /* remapped constants (optional) */
+ if (pIndexMapSet != NULL) {
+ if (!writeChunk(fd, pIndexMapSet->chunkType,
+ pIndexMapSet->chunkData, pIndexMapSet->chunkDataLen))
+ {
+ return false;
+ }
+ }
+
+ /* register maps (optional) */
+ if (pRegMapBuilder != NULL) {
+ if (!writeChunk(fd, (u4) kDexChunkRegisterMaps,
+ pRegMapBuilder->data, pRegMapBuilder->size))
+ {
+ return false;
+ }
+ }
+
+ /* write the end marker */
+ if (!writeChunk(fd, (u4) kDexChunkEnd, NULL, 0)) {
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Log a failed write.
+ */
+static void logFailedWrite(size_t expected, ssize_t actual, const char* msg,
+ int err)
+{
+ LOGE("Write failed: %s (%d of %d): %s\n",
+ msg, (int)actual, (int)expected, strerror(err));
+}
+
+/*
+ * Compute a checksum on a piece of an open file.
+ *
+ * File will be positioned at end of checksummed area.
+ *
+ * Returns "true" on success.
+ */
+static bool computeFileChecksum(int fd, off_t start, size_t length, u4* pSum)
+{
+ unsigned char readBuf[8192];
+ ssize_t actual;
+ uLong adler;
+
+ if (lseek(fd, start, SEEK_SET) != start) {
+ LOGE("Unable to seek to start of checksum area (%ld): %s\n",
+ (long) start, strerror(errno));
+ return false;
+ }
+
+ adler = adler32(0L, Z_NULL, 0);
+
+ while (length != 0) {
+ size_t wanted = (length < sizeof(readBuf)) ? length : sizeof(readBuf);
+ actual = read(fd, readBuf, wanted);
+ if (actual <= 0) {
+ LOGE("Read failed (%d) while computing checksum (len=%zu): %s\n",
+ (int) actual, length, strerror(errno));
+ return false;
+ }
+
+ adler = adler32(adler, readBuf, actual);
+
+ length -= actual;
+ }
+
+ *pSum = adler;
+ return true;
+}
+
+/*
+ * Update the Adler-32 checksum stored in the DEX file. This covers the
+ * swapped and optimized DEX data, but does not include the opt header
+ * or auxillary data.
+ */
+static void updateChecksum(u1* addr, int len, DexHeader* pHeader)
+{
+ /*
+ * Rewrite the checksum. We leave the SHA-1 signature alone.
+ */
+ uLong adler = adler32(0L, Z_NULL, 0);
+ const int nonSum = sizeof(pHeader->magic) + sizeof(pHeader->checksum);
+
+ adler = adler32(adler, addr + nonSum, len - nonSum);
+ pHeader->checksum = adler;
+}
+
diff --git a/vm/analysis/DexOptimize.h b/vm/analysis/DexPrepare.h
similarity index 84%
rename from vm/analysis/DexOptimize.h
rename to vm/analysis/DexPrepare.h
index afcb3cb..ae94979 100644
--- a/vm/analysis/DexOptimize.h
+++ b/vm/analysis/DexPrepare.h
@@ -15,10 +15,10 @@
*/
/*
- * DEX optimization declarations.
+ * DEX preparation declarations.
*/
-#ifndef _DALVIK_DEXOPTIMIZE
-#define _DALVIK_DEXOPTIMIZE
+#ifndef _DALVIK_DEXPREPARE
+#define _DALVIK_DEXPREPARE
/*
* Global DEX optimizer control. Determines the circumstances in which we
@@ -112,18 +112,4 @@
bool dvmContinueOptimization(int fd, off_t dexOffset, long dexLength,
const char* fileName, u4 modWhen, u4 crc, bool isBootstrap);
-/*
- * Abbreviated resolution functions, for use by optimization and verification
- * code.
- */
-ClassObject* dvmOptResolveClass(ClassObject* referrer, u4 classIdx,
- VerifyError* pFailure);
-Method* dvmOptResolveMethod(ClassObject* referrer, u4 methodIdx,
- MethodType methodType, VerifyError* pFailure);
-Method* dvmOptResolveInterfaceMethod(ClassObject* referrer, u4 methodIdx);
-InstField* dvmOptResolveInstField(ClassObject* referrer, u4 ifieldIdx,
- VerifyError* pFailure);
-StaticField* dvmOptResolveStaticField(ClassObject* referrer, u4 sfieldIdx,
- VerifyError* pFailure);
-
-#endif /*_DALVIK_DEXOPTIMIZE*/
+#endif /*_DALVIK_DEXPREPARE*/
diff --git a/vm/analysis/Optimize.c b/vm/analysis/Optimize.c
new file mode 100644
index 0000000..7121229
--- /dev/null
+++ b/vm/analysis/Optimize.c
@@ -0,0 +1,1199 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Perform some simple bytecode optimizations, chiefly "quickening" of
+ * opcodes.
+ */
+#include "Dalvik.h"
+#include "libdex/InstrUtils.h"
+
+#include <zlib.h>
+
+#include <stdlib.h>
+
+/*
+ * Virtual/direct calls to "method" are replaced with an execute-inline
+ * instruction with index "idx".
+ */
+typedef struct InlineSub {
+ Method* method;
+ int inlineIdx;
+} InlineSub;
+
+
+/* fwd */
+static bool loadAllClasses(DvmDex* pDvmDex);
+static void optimizeLoadedClasses(DexFile* pDexFile);
+static void optimizeClass(ClassObject* clazz, const InlineSub* inlineSubs);
+static bool optimizeMethod(Method* method, const InlineSub* inlineSubs);
+static void rewriteInstField(Method* method, u2* insns, OpCode newOpc);
+static bool rewriteVirtualInvoke(Method* method, u2* insns, OpCode newOpc);
+static bool rewriteEmptyDirectInvoke(Method* method, u2* insns);
+static bool rewriteExecuteInline(Method* method, u2* insns,
+ MethodType methodType, const InlineSub* inlineSubs);
+static bool rewriteExecuteInlineRange(Method* method, u2* insns,
+ MethodType methodType, const InlineSub* inlineSubs);
+
+
+/*
+ * Perform in-place rewrites on a memory-mapped DEX file.
+ *
+ * This happens in a short-lived child process, so we can go nutty with
+ * loading classes and allocating memory.
+ */
+bool dvmRewriteDex(u1* addr, int len, bool doVerify, bool doOpt,
+ u4* pHeaderFlags, DexClassLookup** ppClassLookup)
+{
+ u8 prepWhen, loadWhen, verifyWhen, optWhen;
+ DvmDex* pDvmDex = NULL;
+ bool result = false;
+
+ *pHeaderFlags = 0;
+
+ LOGV("+++ swapping bytes\n");
+ if (dexFixByteOrdering(addr, len) != 0)
+ goto bail;
+#if __BYTE_ORDER != __LITTLE_ENDIAN
+ *pHeaderFlags |= DEX_OPT_FLAG_BIG;
+#endif
+
+ /*
+ * Now that the DEX file can be read directly, create a DexFile for it.
+ */
+ if (dvmDexFileOpenPartial(addr, len, &pDvmDex) != 0) {
+ LOGE("Unable to create DexFile\n");
+ goto bail;
+ }
+
+ /*
+ * Create the class lookup table.
+ */
+ //startWhen = dvmGetRelativeTimeUsec();
+ *ppClassLookup = dexCreateClassLookup(pDvmDex->pDexFile);
+ if (*ppClassLookup == NULL)
+ goto bail;
+
+ /*
+ * Bail out early if they don't want The Works. The current implementation
+ * doesn't fork a new process if this flag isn't set, so we really don't
+ * want to continue on with the crazy class loading.
+ */
+ if (!doVerify && !doOpt) {
+ result = true;
+ goto bail;
+ }
+
+ /* this is needed for the next part */
+ pDvmDex->pDexFile->pClassLookup = *ppClassLookup;
+
+ prepWhen = dvmGetRelativeTimeUsec();
+
+ /*
+ * Load all classes found in this DEX file. If they fail to load for
+ * some reason, they won't get verified (which is as it should be).
+ */
+ if (!loadAllClasses(pDvmDex))
+ goto bail;
+ loadWhen = dvmGetRelativeTimeUsec();
+
+ /*
+ * Verify all classes in the DEX file. Export the "is verified" flag
+ * to the DEX file we're creating.
+ */
+ if (doVerify) {
+ dvmVerifyAllClasses(pDvmDex->pDexFile);
+ *pHeaderFlags |= DEX_FLAG_VERIFIED;
+ }
+ verifyWhen = dvmGetRelativeTimeUsec();
+
+ /*
+ * Optimize the classes we successfully loaded. If the opt mode is
+ * OPTIMIZE_MODE_VERIFIED, each class must have been successfully
+ * verified or we'll skip it.
+ */
+#ifndef PROFILE_FIELD_ACCESS
+ if (doOpt) {
+ optimizeLoadedClasses(pDvmDex->pDexFile);
+ *pHeaderFlags |= DEX_OPT_FLAG_FIELDS | DEX_OPT_FLAG_INVOCATIONS;
+ }
+#endif
+ optWhen = dvmGetRelativeTimeUsec();
+
+ LOGD("DexOpt: load %dms, verify %dms, opt %dms\n",
+ (int) (loadWhen - prepWhen) / 1000,
+ (int) (verifyWhen - loadWhen) / 1000,
+ (int) (optWhen - verifyWhen) / 1000);
+
+ result = true;
+
+bail:
+ /* free up storage */
+ dvmDexFileFree(pDvmDex);
+
+ return result;
+}
+
+/*
+ * Try to load all classes in the specified DEX. If they have some sort
+ * of broken dependency, e.g. their superclass lives in a different DEX
+ * that wasn't previously loaded into the bootstrap class path, loading
+ * will fail. This is the desired behavior.
+ *
+ * We have no notion of class loader at this point, so we load all of
+ * the classes with the bootstrap class loader. It turns out this has
+ * exactly the behavior we want, and has no ill side effects because we're
+ * running in a separate process and anything we load here will be forgotten.
+ *
+ * We set the CLASS_MULTIPLE_DEFS flag here if we see multiple definitions.
+ * This works because we only call here as part of optimization / pre-verify,
+ * not during verification as part of loading a class into a running VM.
+ *
+ * This returns "false" if the world is too screwed up to do anything
+ * useful at all.
+ */
+static bool loadAllClasses(DvmDex* pDvmDex)
+{
+ u4 count = pDvmDex->pDexFile->pHeader->classDefsSize;
+ u4 idx;
+ int loaded = 0;
+
+ LOGV("DexOpt: +++ trying to load %d classes\n", count);
+
+ dvmSetBootPathExtraDex(pDvmDex);
+
+ /*
+ * We have some circularity issues with Class and Object that are most
+ * easily avoided by ensuring that Object is never the first thing we
+ * try to find. Take care of that here. (We only need to do this when
+ * loading classes from the DEX file that contains Object, and only
+ * when Object comes first in the list, but it costs very little to
+ * do it in all cases.)
+ */
+ if (dvmFindSystemClass("Ljava/lang/Class;") == NULL) {
+ LOGE("ERROR: java.lang.Class does not exist!\n");
+ return false;
+ }
+
+ for (idx = 0; idx < count; idx++) {
+ const DexClassDef* pClassDef;
+ const char* classDescriptor;
+ ClassObject* newClass;
+
+ pClassDef = dexGetClassDef(pDvmDex->pDexFile, idx);
+ classDescriptor =
+ dexStringByTypeIdx(pDvmDex->pDexFile, pClassDef->classIdx);
+
+ LOGV("+++ loading '%s'", classDescriptor);
+ //newClass = dvmDefineClass(pDexFile, classDescriptor,
+ // NULL);
+ newClass = dvmFindSystemClassNoInit(classDescriptor);
+ if (newClass == NULL) {
+ LOGV("DexOpt: failed loading '%s'\n", classDescriptor);
+ dvmClearOptException(dvmThreadSelf());
+ } else if (newClass->pDvmDex != pDvmDex) {
+ /*
+ * We don't load the new one, and we tag the first one found
+ * with the "multiple def" flag so the resolver doesn't try
+ * to make it available.
+ */
+ LOGD("DexOpt: '%s' has an earlier definition; blocking out\n",
+ classDescriptor);
+ SET_CLASS_FLAG(newClass, CLASS_MULTIPLE_DEFS);
+ } else {
+ loaded++;
+ }
+ }
+ LOGV("DexOpt: +++ successfully loaded %d classes\n", loaded);
+
+ dvmSetBootPathExtraDex(NULL);
+ return true;
+}
+
+
+/*
+ * Create a table of inline substitutions.
+ *
+ * TODO: this is currently just a linear array. We will want to put this
+ * into a hash table as the list size increases.
+ */
+static InlineSub* createInlineSubsTable(void)
+{
+ const InlineOperation* ops = dvmGetInlineOpsTable();
+ const int count = dvmGetInlineOpsTableLength();
+ InlineSub* table;
+ Method* method;
+ ClassObject* clazz;
+ int i, tableIndex;
+
+ /*
+ * Allocate for optimism: one slot per entry, plus an end-of-list marker.
+ */
+ table = malloc(sizeof(InlineSub) * (count+1));
+
+ tableIndex = 0;
+ for (i = 0; i < count; i++) {
+ clazz = dvmFindClassNoInit(ops[i].classDescriptor, NULL);
+ if (clazz == NULL) {
+ LOGV("DexOpt: can't inline for class '%s': not found\n",
+ ops[i].classDescriptor);
+ dvmClearOptException(dvmThreadSelf());
+ } else {
+ /*
+ * Method could be virtual or direct. Try both. Don't use
+ * the "hier" versions.
+ */
+ method = dvmFindDirectMethodByDescriptor(clazz, ops[i].methodName,
+ ops[i].methodSignature);
+ if (method == NULL)
+ method = dvmFindVirtualMethodByDescriptor(clazz, ops[i].methodName,
+ ops[i].methodSignature);
+ if (method == NULL) {
+ LOGW("DexOpt: can't inline %s.%s %s: method not found\n",
+ ops[i].classDescriptor, ops[i].methodName,
+ ops[i].methodSignature);
+ } else {
+ if (!dvmIsFinalClass(clazz) && !dvmIsFinalMethod(method)) {
+ LOGW("DexOpt: WARNING: inline op on non-final class/method "
+ "%s.%s\n",
+ clazz->descriptor, method->name);
+ /* fail? */
+ }
+ if (dvmIsSynchronizedMethod(method) ||
+ dvmIsDeclaredSynchronizedMethod(method))
+ {
+ LOGW("DexOpt: WARNING: inline op on synchronized method "
+ "%s.%s\n",
+ clazz->descriptor, method->name);
+ /* fail? */
+ }
+
+ table[tableIndex].method = method;
+ table[tableIndex].inlineIdx = i;
+ tableIndex++;
+
+ LOGV("DexOpt: will inline %d: %s.%s %s\n", i,
+ ops[i].classDescriptor, ops[i].methodName,
+ ops[i].methodSignature);
+ }
+ }
+ }
+
+ /* mark end of table */
+ table[tableIndex].method = NULL;
+ LOGV("DexOpt: inline table has %d entries\n", tableIndex);
+
+ return table;
+}
+
+/*
+ * Run through all classes that were successfully loaded from this DEX
+ * file and optimize their code sections.
+ */
+static void optimizeLoadedClasses(DexFile* pDexFile)
+{
+ u4 count = pDexFile->pHeader->classDefsSize;
+ u4 idx;
+ InlineSub* inlineSubs = NULL;
+
+ LOGV("DexOpt: +++ optimizing up to %d classes\n", count);
+ assert(gDvm.dexOptMode != OPTIMIZE_MODE_NONE);
+
+ inlineSubs = createInlineSubsTable();
+
+ for (idx = 0; idx < count; idx++) {
+ const DexClassDef* pClassDef;
+ const char* classDescriptor;
+ ClassObject* clazz;
+
+ pClassDef = dexGetClassDef(pDexFile, idx);
+ classDescriptor = dexStringByTypeIdx(pDexFile, pClassDef->classIdx);
+
+ /* all classes are loaded into the bootstrap class loader */
+ clazz = dvmLookupClass(classDescriptor, NULL, false);
+ if (clazz != NULL) {
+ if ((pClassDef->accessFlags & CLASS_ISPREVERIFIED) == 0 &&
+ gDvm.dexOptMode == OPTIMIZE_MODE_VERIFIED)
+ {
+ LOGV("DexOpt: not optimizing '%s': not verified\n",
+ classDescriptor);
+ } else if (clazz->pDvmDex->pDexFile != pDexFile) {
+ /* shouldn't be here -- verifier should have caught */
+ LOGD("DexOpt: not optimizing '%s': multiple definitions\n",
+ classDescriptor);
+ } else {
+ optimizeClass(clazz, inlineSubs);
+
+ /* set the flag whether or not we actually did anything */
+ ((DexClassDef*)pClassDef)->accessFlags |=
+ CLASS_ISOPTIMIZED;
+ }
+ } else {
+ LOGV("DexOpt: not optimizing unavailable class '%s'\n",
+ classDescriptor);
+ }
+ }
+
+ free(inlineSubs);
+}
+
+/*
+ * Optimize the specified class.
+ */
+static void optimizeClass(ClassObject* clazz, const InlineSub* inlineSubs)
+{
+ int i;
+
+ for (i = 0; i < clazz->directMethodCount; i++) {
+ if (!optimizeMethod(&clazz->directMethods[i], inlineSubs))
+ goto fail;
+ }
+ for (i = 0; i < clazz->virtualMethodCount; i++) {
+ if (!optimizeMethod(&clazz->virtualMethods[i], inlineSubs))
+ goto fail;
+ }
+
+ return;
+
+fail:
+ LOGV("DexOpt: ceasing optimization attempts on %s\n", clazz->descriptor);
+}
+
+/*
+ * Optimize instructions in a method.
+ *
+ * Returns "true" if all went well, "false" if we bailed out early when
+ * something failed.
+ */
+static bool optimizeMethod(Method* method, const InlineSub* inlineSubs)
+{
+ u4 insnsSize;
+ u2* insns;
+ u2 inst;
+
+ if (dvmIsNativeMethod(method) || dvmIsAbstractMethod(method))
+ return true;
+
+ insns = (u2*) method->insns;
+ assert(insns != NULL);
+ insnsSize = dvmGetMethodInsnsSize(method);
+
+ while (insnsSize > 0) {
+ int width;
+
+ inst = *insns & 0xff;
+
+ switch (inst) {
+ case OP_IGET:
+ case OP_IGET_BOOLEAN:
+ case OP_IGET_BYTE:
+ case OP_IGET_CHAR:
+ case OP_IGET_SHORT:
+ rewriteInstField(method, insns, OP_IGET_QUICK);
+ break;
+ case OP_IGET_WIDE:
+ rewriteInstField(method, insns, OP_IGET_WIDE_QUICK);
+ break;
+ case OP_IGET_OBJECT:
+ rewriteInstField(method, insns, OP_IGET_OBJECT_QUICK);
+ break;
+ case OP_IPUT:
+ case OP_IPUT_BOOLEAN:
+ case OP_IPUT_BYTE:
+ case OP_IPUT_CHAR:
+ case OP_IPUT_SHORT:
+ rewriteInstField(method, insns, OP_IPUT_QUICK);
+ break;
+ case OP_IPUT_WIDE:
+ rewriteInstField(method, insns, OP_IPUT_WIDE_QUICK);
+ break;
+ case OP_IPUT_OBJECT:
+ rewriteInstField(method, insns, OP_IPUT_OBJECT_QUICK);
+ break;
+
+ case OP_INVOKE_VIRTUAL:
+ if (!rewriteExecuteInline(method, insns, METHOD_VIRTUAL,inlineSubs))
+ {
+ if (!rewriteVirtualInvoke(method, insns, OP_INVOKE_VIRTUAL_QUICK))
+ return false;
+ }
+ break;
+ case OP_INVOKE_VIRTUAL_RANGE:
+ if (!rewriteExecuteInlineRange(method, insns, METHOD_VIRTUAL,
+ inlineSubs))
+ {
+ if (!rewriteVirtualInvoke(method, insns,
+ OP_INVOKE_VIRTUAL_QUICK_RANGE))
+ {
+ return false;
+ }
+ }
+ break;
+ case OP_INVOKE_SUPER:
+ if (!rewriteVirtualInvoke(method, insns, OP_INVOKE_SUPER_QUICK))
+ return false;
+ break;
+ case OP_INVOKE_SUPER_RANGE:
+ if (!rewriteVirtualInvoke(method, insns, OP_INVOKE_SUPER_QUICK_RANGE))
+ return false;
+ break;
+
+ case OP_INVOKE_DIRECT:
+ if (!rewriteExecuteInline(method, insns, METHOD_DIRECT, inlineSubs))
+ {
+ if (!rewriteEmptyDirectInvoke(method, insns))
+ return false;
+ }
+ break;
+ case OP_INVOKE_DIRECT_RANGE:
+ rewriteExecuteInlineRange(method, insns, METHOD_DIRECT, inlineSubs);
+ break;
+
+ case OP_INVOKE_STATIC:
+ rewriteExecuteInline(method, insns, METHOD_STATIC, inlineSubs);
+ break;
+ case OP_INVOKE_STATIC_RANGE:
+ rewriteExecuteInlineRange(method, insns, METHOD_STATIC, inlineSubs);
+ break;
+
+ default:
+ // ignore this instruction
+ ;
+ }
+
+ if (*insns == kPackedSwitchSignature) {
+ width = 4 + insns[1] * 2;
+ } else if (*insns == kSparseSwitchSignature) {
+ width = 2 + insns[1] * 4;
+ } else if (*insns == kArrayDataSignature) {
+ u2 elemWidth = insns[1];
+ u4 len = insns[2] | (((u4)insns[3]) << 16);
+ width = 4 + (elemWidth * len + 1) / 2;
+ } else {
+ width = dexGetInstrWidthAbs(gDvm.instrWidth, inst);
+ }
+ assert(width > 0);
+
+ insns += width;
+ insnsSize -= width;
+ }
+
+ assert(insnsSize == 0);
+ return true;
+}
+
+
+/*
+ * If "referrer" and "resClass" don't come from the same DEX file, and
+ * the DEX we're working on is not destined for the bootstrap class path,
+ * tweak the class loader so package-access checks work correctly.
+ *
+ * Only do this if we're doing pre-verification or optimization.
+ */
+static void tweakLoader(ClassObject* referrer, ClassObject* resClass)
+{
+ if (!gDvm.optimizing)
+ return;
+ assert(referrer->classLoader == NULL);
+ assert(resClass->classLoader == NULL);
+
+ if (!gDvm.optimizingBootstrapClass) {
+ /* class loader for an array class comes from element type */
+ if (dvmIsArrayClass(resClass))
+ resClass = resClass->elementClass;
+ if (referrer->pDvmDex != resClass->pDvmDex)
+ resClass->classLoader = (Object*) 0xdead3333;
+ }
+}
+
+/*
+ * Undo the effects of tweakLoader.
+ */
+static void untweakLoader(ClassObject* referrer, ClassObject* resClass)
+{
+ if (!gDvm.optimizing || gDvm.optimizingBootstrapClass)
+ return;
+
+ if (dvmIsArrayClass(resClass))
+ resClass = resClass->elementClass;
+ resClass->classLoader = NULL;
+}
+
+
+/*
+ * Alternate version of dvmResolveClass for use with verification and
+ * optimization. Performs access checks on every resolve, and refuses
+ * to acknowledge the existence of classes defined in more than one DEX
+ * file.
+ *
+ * Exceptions caused by failures are cleared before returning.
+ *
+ * On failure, returns NULL, and sets *pFailure if pFailure is not NULL.
+ */
+ClassObject* dvmOptResolveClass(ClassObject* referrer, u4 classIdx,
+ VerifyError* pFailure)
+{
+ DvmDex* pDvmDex = referrer->pDvmDex;
+ ClassObject* resClass;
+
+ /*
+ * Check the table first. If not there, do the lookup by name.
+ */
+ resClass = dvmDexGetResolvedClass(pDvmDex, classIdx);
+ if (resClass == NULL) {
+ const char* className = dexStringByTypeIdx(pDvmDex->pDexFile, classIdx);
+ if (className[0] != '\0' && className[1] == '\0') {
+ /* primitive type */
+ resClass = dvmFindPrimitiveClass(className[0]);
+ } else {
+ resClass = dvmFindClassNoInit(className, referrer->classLoader);
+ }
+ if (resClass == NULL) {
+ /* not found, exception should be raised */
+ LOGV("DexOpt: class %d (%s) not found\n",
+ classIdx,
+ dexStringByTypeIdx(pDvmDex->pDexFile, classIdx));
+ if (pFailure != NULL) {
+ /* dig through the wrappers to find the original failure */
+ Object* excep = dvmGetException(dvmThreadSelf());
+ while (true) {
+ Object* cause = dvmGetExceptionCause(excep);
+ if (cause == NULL)
+ break;
+ excep = cause;
+ }
+ if (strcmp(excep->clazz->descriptor,
+ "Ljava/lang/IncompatibleClassChangeError;") == 0)
+ {
+ *pFailure = VERIFY_ERROR_CLASS_CHANGE;
+ } else {
+ *pFailure = VERIFY_ERROR_NO_CLASS;
+ }
+ }
+ dvmClearOptException(dvmThreadSelf());
+ return NULL;
+ }
+
+ /*
+ * Add it to the resolved table so we're faster on the next lookup.
+ */
+ dvmDexSetResolvedClass(pDvmDex, classIdx, resClass);
+ }
+
+ /* multiple definitions? */
+ if (IS_CLASS_FLAG_SET(resClass, CLASS_MULTIPLE_DEFS)) {
+ LOGI("DexOpt: not resolving ambiguous class '%s'\n",
+ resClass->descriptor);
+ if (pFailure != NULL)
+ *pFailure = VERIFY_ERROR_NO_CLASS;
+ return NULL;
+ }
+
+ /* access allowed? */
+ tweakLoader(referrer, resClass);
+ bool allowed = dvmCheckClassAccess(referrer, resClass);
+ untweakLoader(referrer, resClass);
+ if (!allowed) {
+ LOGW("DexOpt: resolve class illegal access: %s -> %s\n",
+ referrer->descriptor, resClass->descriptor);
+ if (pFailure != NULL)
+ *pFailure = VERIFY_ERROR_ACCESS_CLASS;
+ return NULL;
+ }
+
+ return resClass;
+}
+
+/*
+ * Alternate version of dvmResolveInstField().
+ *
+ * On failure, returns NULL, and sets *pFailure if pFailure is not NULL.
+ */
+InstField* dvmOptResolveInstField(ClassObject* referrer, u4 ifieldIdx,
+ VerifyError* pFailure)
+{
+ DvmDex* pDvmDex = referrer->pDvmDex;
+ InstField* resField;
+
+ resField = (InstField*) dvmDexGetResolvedField(pDvmDex, ifieldIdx);
+ if (resField == NULL) {
+ const DexFieldId* pFieldId;
+ ClassObject* resClass;
+
+ pFieldId = dexGetFieldId(pDvmDex->pDexFile, ifieldIdx);
+
+ /*
+ * Find the field's class.
+ */
+ resClass = dvmOptResolveClass(referrer, pFieldId->classIdx, pFailure);
+ if (resClass == NULL) {
+ //dvmClearOptException(dvmThreadSelf());
+ assert(!dvmCheckException(dvmThreadSelf()));
+ if (pFailure != NULL) { assert(!VERIFY_OK(*pFailure)); }
+ return NULL;
+ }
+
+ resField = (InstField*)dvmFindFieldHier(resClass,
+ dexStringById(pDvmDex->pDexFile, pFieldId->nameIdx),
+ dexStringByTypeIdx(pDvmDex->pDexFile, pFieldId->typeIdx));
+ if (resField == NULL) {
+ LOGD("DexOpt: couldn't find field %s.%s\n",
+ resClass->descriptor,
+ dexStringById(pDvmDex->pDexFile, pFieldId->nameIdx));
+ if (pFailure != NULL)
+ *pFailure = VERIFY_ERROR_NO_FIELD;
+ return NULL;
+ }
+ if (dvmIsStaticField(&resField->field)) {
+ LOGD("DexOpt: wanted instance, got static for field %s.%s\n",
+ resClass->descriptor,
+ dexStringById(pDvmDex->pDexFile, pFieldId->nameIdx));
+ if (pFailure != NULL)
+ *pFailure = VERIFY_ERROR_CLASS_CHANGE;
+ return NULL;
+ }
+
+ /*
+ * Add it to the resolved table so we're faster on the next lookup.
+ */
+ dvmDexSetResolvedField(pDvmDex, ifieldIdx, (Field*) resField);
+ }
+
+ /* access allowed? */
+ tweakLoader(referrer, resField->field.clazz);
+ bool allowed = dvmCheckFieldAccess(referrer, (Field*)resField);
+ untweakLoader(referrer, resField->field.clazz);
+ if (!allowed) {
+ LOGI("DexOpt: access denied from %s to field %s.%s\n",
+ referrer->descriptor, resField->field.clazz->descriptor,
+ resField->field.name);
+ if (pFailure != NULL)
+ *pFailure = VERIFY_ERROR_ACCESS_FIELD;
+ return NULL;
+ }
+
+ return resField;
+}
+
+/*
+ * Alternate version of dvmResolveStaticField().
+ *
+ * Does not force initialization of the resolved field's class.
+ *
+ * On failure, returns NULL, and sets *pFailure if pFailure is not NULL.
+ */
+StaticField* dvmOptResolveStaticField(ClassObject* referrer, u4 sfieldIdx,
+ VerifyError* pFailure)
+{
+ DvmDex* pDvmDex = referrer->pDvmDex;
+ StaticField* resField;
+
+ resField = (StaticField*)dvmDexGetResolvedField(pDvmDex, sfieldIdx);
+ if (resField == NULL) {
+ const DexFieldId* pFieldId;
+ ClassObject* resClass;
+
+ pFieldId = dexGetFieldId(pDvmDex->pDexFile, sfieldIdx);
+
+ /*
+ * Find the field's class.
+ */
+ resClass = dvmOptResolveClass(referrer, pFieldId->classIdx, pFailure);
+ if (resClass == NULL) {
+ //dvmClearOptException(dvmThreadSelf());
+ assert(!dvmCheckException(dvmThreadSelf()));
+ if (pFailure != NULL) { assert(!VERIFY_OK(*pFailure)); }
+ return NULL;
+ }
+
+ resField = (StaticField*)dvmFindFieldHier(resClass,
+ dexStringById(pDvmDex->pDexFile, pFieldId->nameIdx),
+ dexStringByTypeIdx(pDvmDex->pDexFile, pFieldId->typeIdx));
+ if (resField == NULL) {
+ LOGD("DexOpt: couldn't find static field\n");
+ if (pFailure != NULL)
+ *pFailure = VERIFY_ERROR_NO_FIELD;
+ return NULL;
+ }
+ if (!dvmIsStaticField(&resField->field)) {
+ LOGD("DexOpt: wanted static, got instance for field %s.%s\n",
+ resClass->descriptor,
+ dexStringById(pDvmDex->pDexFile, pFieldId->nameIdx));
+ if (pFailure != NULL)
+ *pFailure = VERIFY_ERROR_CLASS_CHANGE;
+ return NULL;
+ }
+
+ /*
+ * Add it to the resolved table so we're faster on the next lookup.
+ *
+ * We can only do this if we're in "dexopt", because the presence
+ * of a valid value in the resolution table implies that the class
+ * containing the static field has been initialized.
+ */
+ if (gDvm.optimizing)
+ dvmDexSetResolvedField(pDvmDex, sfieldIdx, (Field*) resField);
+ }
+
+ /* access allowed? */
+ tweakLoader(referrer, resField->field.clazz);
+ bool allowed = dvmCheckFieldAccess(referrer, (Field*)resField);
+ untweakLoader(referrer, resField->field.clazz);
+ if (!allowed) {
+ LOGI("DexOpt: access denied from %s to field %s.%s\n",
+ referrer->descriptor, resField->field.clazz->descriptor,
+ resField->field.name);
+ if (pFailure != NULL)
+ *pFailure = VERIFY_ERROR_ACCESS_FIELD;
+ return NULL;
+ }
+
+ return resField;
+}
+
+
+/*
+ * Rewrite an iget/iput instruction. These all have the form:
+ * op vA, vB, field@CCCC
+ *
+ * Where vA holds the value, vB holds the object reference, and CCCC is
+ * the field reference constant pool offset. We want to replace CCCC
+ * with the byte offset from the start of the object.
+ *
+ * "clazz" is the referring class. We need this because we verify
+ * access rights here.
+ */
+static void rewriteInstField(Method* method, u2* insns, OpCode newOpc)
+{
+ ClassObject* clazz = method->clazz;
+ u2 fieldIdx = insns[1];
+ InstField* field;
+ int byteOffset;
+
+ field = dvmOptResolveInstField(clazz, fieldIdx, NULL);
+ if (field == NULL) {
+ LOGI("DexOpt: unable to optimize field ref 0x%04x at 0x%02x in %s.%s\n",
+ fieldIdx, (int) (insns - method->insns), clazz->descriptor,
+ method->name);
+ return;
+ }
+
+ if (field->byteOffset >= 65536) {
+ LOGI("DexOpt: field offset exceeds 64K (%d)\n", field->byteOffset);
+ return;
+ }
+
+ insns[0] = (insns[0] & 0xff00) | (u2) newOpc;
+ insns[1] = (u2) field->byteOffset;
+ LOGVV("DexOpt: rewrote access to %s.%s --> %d\n",
+ field->field.clazz->descriptor, field->field.name,
+ field->byteOffset);
+}
+
+/*
+ * Alternate version of dvmResolveMethod().
+ *
+ * Doesn't throw exceptions, and checks access on every lookup.
+ *
+ * On failure, returns NULL, and sets *pFailure if pFailure is not NULL.
+ */
+Method* dvmOptResolveMethod(ClassObject* referrer, u4 methodIdx,
+ MethodType methodType, VerifyError* pFailure)
+{
+ DvmDex* pDvmDex = referrer->pDvmDex;
+ Method* resMethod;
+
+ assert(methodType == METHOD_DIRECT ||
+ methodType == METHOD_VIRTUAL ||
+ methodType == METHOD_STATIC);
+
+ LOGVV("--- resolving method %u (referrer=%s)\n", methodIdx,
+ referrer->descriptor);
+
+ resMethod = dvmDexGetResolvedMethod(pDvmDex, methodIdx);
+ if (resMethod == NULL) {
+ const DexMethodId* pMethodId;
+ ClassObject* resClass;
+
+ pMethodId = dexGetMethodId(pDvmDex->pDexFile, methodIdx);
+
+ resClass = dvmOptResolveClass(referrer, pMethodId->classIdx, pFailure);
+ if (resClass == NULL) {
+ /*
+ * Can't find the class that the method is a part of, or don't
+ * have permission to access the class.
+ */
+ LOGV("DexOpt: can't find called method's class (?.%s)\n",
+ dexStringById(pDvmDex->pDexFile, pMethodId->nameIdx));
+ if (pFailure != NULL) { assert(!VERIFY_OK(*pFailure)); }
+ return NULL;
+ }
+ if (dvmIsInterfaceClass(resClass)) {
+ /* method is part of an interface; this is wrong method for that */
+ LOGW("DexOpt: method is in an interface\n");
+ if (pFailure != NULL)
+ *pFailure = VERIFY_ERROR_GENERIC;
+ return NULL;
+ }
+
+ /*
+ * We need to chase up the class hierarchy to find methods defined
+ * in super-classes. (We only want to check the current class
+ * if we're looking for a constructor.)
+ */
+ DexProto proto;
+ dexProtoSetFromMethodId(&proto, pDvmDex->pDexFile, pMethodId);
+
+ if (methodType == METHOD_DIRECT) {
+ resMethod = dvmFindDirectMethod(resClass,
+ dexStringById(pDvmDex->pDexFile, pMethodId->nameIdx), &proto);
+ } else {
+ /* METHOD_STATIC or METHOD_VIRTUAL */
+ resMethod = dvmFindMethodHier(resClass,
+ dexStringById(pDvmDex->pDexFile, pMethodId->nameIdx), &proto);
+ }
+
+ if (resMethod == NULL) {
+ LOGV("DexOpt: couldn't find method '%s'\n",
+ dexStringById(pDvmDex->pDexFile, pMethodId->nameIdx));
+ if (pFailure != NULL)
+ *pFailure = VERIFY_ERROR_NO_METHOD;
+ return NULL;
+ }
+ if (methodType == METHOD_STATIC) {
+ if (!dvmIsStaticMethod(resMethod)) {
+ LOGD("DexOpt: wanted static, got instance for method %s.%s\n",
+ resClass->descriptor, resMethod->name);
+ if (pFailure != NULL)
+ *pFailure = VERIFY_ERROR_CLASS_CHANGE;
+ return NULL;
+ }
+ } else if (methodType == METHOD_VIRTUAL) {
+ if (dvmIsStaticMethod(resMethod)) {
+ LOGD("DexOpt: wanted instance, got static for method %s.%s\n",
+ resClass->descriptor, resMethod->name);
+ if (pFailure != NULL)
+ *pFailure = VERIFY_ERROR_CLASS_CHANGE;
+ return NULL;
+ }
+ }
+
+ /* see if this is a pure-abstract method */
+ if (dvmIsAbstractMethod(resMethod) && !dvmIsAbstractClass(resClass)) {
+ LOGW("DexOpt: pure-abstract method '%s' in %s\n",
+ dexStringById(pDvmDex->pDexFile, pMethodId->nameIdx),
+ resClass->descriptor);
+ if (pFailure != NULL)
+ *pFailure = VERIFY_ERROR_GENERIC;
+ return NULL;
+ }
+
+ /*
+ * Add it to the resolved table so we're faster on the next lookup.
+ *
+ * We can only do this for static methods if we're not in "dexopt",
+ * because the presence of a valid value in the resolution table
+ * implies that the class containing the static field has been
+ * initialized.
+ */
+ if (methodType != METHOD_STATIC || gDvm.optimizing)
+ dvmDexSetResolvedMethod(pDvmDex, methodIdx, resMethod);
+ }
+
+ LOGVV("--- found method %d (%s.%s)\n",
+ methodIdx, resMethod->clazz->descriptor, resMethod->name);
+
+ /* access allowed? */
+ tweakLoader(referrer, resMethod->clazz);
+ bool allowed = dvmCheckMethodAccess(referrer, resMethod);
+ untweakLoader(referrer, resMethod->clazz);
+ if (!allowed) {
+ IF_LOGI() {
+ char* desc = dexProtoCopyMethodDescriptor(&resMethod->prototype);
+ LOGI("DexOpt: illegal method access (call %s.%s %s from %s)\n",
+ resMethod->clazz->descriptor, resMethod->name, desc,
+ referrer->descriptor);
+ free(desc);
+ }
+ if (pFailure != NULL)
+ *pFailure = VERIFY_ERROR_ACCESS_METHOD;
+ return NULL;
+ }
+
+ return resMethod;
+}
+
+/*
+ * Rewrite invoke-virtual, invoke-virtual/range, invoke-super, and
+ * invoke-super/range. These all have the form:
+ * op vAA, meth@BBBB, reg stuff @CCCC
+ *
+ * We want to replace the method constant pool index BBBB with the
+ * vtable index.
+ */
+static bool rewriteVirtualInvoke(Method* method, u2* insns, OpCode newOpc)
+{
+ ClassObject* clazz = method->clazz;
+ Method* baseMethod;
+ u2 methodIdx = insns[1];
+
+ baseMethod = dvmOptResolveMethod(clazz, methodIdx, METHOD_VIRTUAL, NULL);
+ if (baseMethod == NULL) {
+ LOGD("DexOpt: unable to optimize virt call 0x%04x at 0x%02x in %s.%s\n",
+ methodIdx,
+ (int) (insns - method->insns), clazz->descriptor,
+ method->name);
+ return false;
+ }
+
+ assert((insns[0] & 0xff) == OP_INVOKE_VIRTUAL ||
+ (insns[0] & 0xff) == OP_INVOKE_VIRTUAL_RANGE ||
+ (insns[0] & 0xff) == OP_INVOKE_SUPER ||
+ (insns[0] & 0xff) == OP_INVOKE_SUPER_RANGE);
+
+ /*
+ * Note: Method->methodIndex is a u2 and is range checked during the
+ * initial load.
+ */
+ insns[0] = (insns[0] & 0xff00) | (u2) newOpc;
+ insns[1] = baseMethod->methodIndex;
+
+ //LOGI("DexOpt: rewrote call to %s.%s --> %s.%s\n",
+ // method->clazz->descriptor, method->name,
+ // baseMethod->clazz->descriptor, baseMethod->name);
+
+ return true;
+}
+
+/*
+ * Rewrite invoke-direct, which has the form:
+ * op vAA, meth@BBBB, reg stuff @CCCC
+ *
+ * There isn't a lot we can do to make this faster, but in some situations
+ * we can make it go away entirely.
+ *
+ * This must only be used when the invoked method does nothing and has
+ * no return value (the latter being very important for verification).
+ */
+static bool rewriteEmptyDirectInvoke(Method* method, u2* insns)
+{
+ ClassObject* clazz = method->clazz;
+ Method* calledMethod;
+ u2 methodIdx = insns[1];
+
+ calledMethod = dvmOptResolveMethod(clazz, methodIdx, METHOD_DIRECT, NULL);
+ if (calledMethod == NULL) {
+ LOGD("DexOpt: unable to opt direct call 0x%04x at 0x%02x in %s.%s\n",
+ methodIdx,
+ (int) (insns - method->insns), clazz->descriptor,
+ method->name);
+ return false;
+ }
+
+ /* TODO: verify that java.lang.Object() is actually empty! */
+ if (calledMethod->clazz == gDvm.classJavaLangObject &&
+ dvmCompareNameDescriptorAndMethod("<init>", "()V", calledMethod) == 0)
+ {
+ /*
+ * Replace with "empty" instruction. DO NOT disturb anything
+ * else about it, as we want it to function the same as
+ * OP_INVOKE_DIRECT when debugging is enabled.
+ */
+ assert((insns[0] & 0xff) == OP_INVOKE_DIRECT);
+ insns[0] = (insns[0] & 0xff00) | (u2) OP_INVOKE_DIRECT_EMPTY;
+
+ //LOGI("DexOpt: marked-empty call to %s.%s --> %s.%s\n",
+ // method->clazz->descriptor, method->name,
+ // calledMethod->clazz->descriptor, calledMethod->name);
+ }
+
+ return true;
+}
+
+/*
+ * Resolve an interface method reference.
+ *
+ * No method access check here -- interface methods are always public.
+ *
+ * Returns NULL if the method was not found. Does not throw an exception.
+ */
+Method* dvmOptResolveInterfaceMethod(ClassObject* referrer, u4 methodIdx)
+{
+ DvmDex* pDvmDex = referrer->pDvmDex;
+ Method* resMethod;
+ int i;
+
+ LOGVV("--- resolving interface method %d (referrer=%s)\n",
+ methodIdx, referrer->descriptor);
+
+ resMethod = dvmDexGetResolvedMethod(pDvmDex, methodIdx);
+ if (resMethod == NULL) {
+ const DexMethodId* pMethodId;
+ ClassObject* resClass;
+
+ pMethodId = dexGetMethodId(pDvmDex->pDexFile, methodIdx);
+
+ resClass = dvmOptResolveClass(referrer, pMethodId->classIdx, NULL);
+ if (resClass == NULL) {
+ /* can't find the class that the method is a part of */
+ dvmClearOptException(dvmThreadSelf());
+ return NULL;
+ }
+ if (!dvmIsInterfaceClass(resClass)) {
+ /* whoops */
+ LOGI("Interface method not part of interface class\n");
+ return NULL;
+ }
+
+ const char* methodName =
+ dexStringById(pDvmDex->pDexFile, pMethodId->nameIdx);
+ DexProto proto;
+ dexProtoSetFromMethodId(&proto, pDvmDex->pDexFile, pMethodId);
+
+ LOGVV("+++ looking for '%s' '%s' in resClass='%s'\n",
+ methodName, methodSig, resClass->descriptor);
+ resMethod = dvmFindVirtualMethod(resClass, methodName, &proto);
+ if (resMethod == NULL) {
+ /* scan superinterfaces and superclass interfaces */
+ LOGVV("+++ did not resolve immediately\n");
+ for (i = 0; i < resClass->iftableCount; i++) {
+ resMethod = dvmFindVirtualMethod(resClass->iftable[i].clazz,
+ methodName, &proto);
+ if (resMethod != NULL)
+ break;
+ }
+
+ if (resMethod == NULL) {
+ LOGVV("+++ unable to resolve method %s\n", methodName);
+ return NULL;
+ }
+ } else {
+ LOGVV("+++ resolved immediately: %s (%s %d)\n", resMethod->name,
+ resMethod->clazz->descriptor, (u4) resMethod->methodIndex);
+ }
+
+ /* we're expecting this to be abstract */
+ if (!dvmIsAbstractMethod(resMethod)) {
+ char* desc = dexProtoCopyMethodDescriptor(&resMethod->prototype);
+ LOGW("Found non-abstract interface method %s.%s %s\n",
+ resMethod->clazz->descriptor, resMethod->name, desc);
+ free(desc);
+ return NULL;
+ }
+
+ /*
+ * Add it to the resolved table so we're faster on the next lookup.
+ */
+ dvmDexSetResolvedMethod(pDvmDex, methodIdx, resMethod);
+ }
+
+ LOGVV("--- found interface method %d (%s.%s)\n",
+ methodIdx, resMethod->clazz->descriptor, resMethod->name);
+
+ /* interface methods are always public; no need to check access */
+
+ return resMethod;
+}
+
+/*
+ * See if the method being called can be rewritten as an inline operation.
+ * Works for invoke-virtual, invoke-direct, and invoke-static.
+ *
+ * Returns "true" if we replace it.
+ */
+static bool rewriteExecuteInline(Method* method, u2* insns,
+ MethodType methodType, const InlineSub* inlineSubs)
+{
+ ClassObject* clazz = method->clazz;
+ Method* calledMethod;
+ u2 methodIdx = insns[1];
+
+ //return false;
+
+ calledMethod = dvmOptResolveMethod(clazz, methodIdx, methodType, NULL);
+ if (calledMethod == NULL) {
+ LOGV("+++ DexOpt inline: can't find %d\n", methodIdx);
+ return false;
+ }
+
+ while (inlineSubs->method != NULL) {
+ /*
+ if (extra) {
+ LOGI("comparing %p vs %p %s.%s %s\n",
+ inlineSubs->method, calledMethod,
+ inlineSubs->method->clazz->descriptor,
+ inlineSubs->method->name,
+ inlineSubs->method->signature);
+ }
+ */
+ if (inlineSubs->method == calledMethod) {
+ assert((insns[0] & 0xff) == OP_INVOKE_DIRECT ||
+ (insns[0] & 0xff) == OP_INVOKE_STATIC ||
+ (insns[0] & 0xff) == OP_INVOKE_VIRTUAL);
+ insns[0] = (insns[0] & 0xff00) | (u2) OP_EXECUTE_INLINE;
+ insns[1] = (u2) inlineSubs->inlineIdx;
+
+ //LOGI("DexOpt: execute-inline %s.%s --> %s.%s\n",
+ // method->clazz->descriptor, method->name,
+ // calledMethod->clazz->descriptor, calledMethod->name);
+ return true;
+ }
+
+ inlineSubs++;
+ }
+
+ return false;
+}
+
+/*
+ * See if the method being called can be rewritten as an inline operation.
+ * Works for invoke-virtual/range, invoke-direct/range, and invoke-static/range.
+ *
+ * Returns "true" if we replace it.
+ */
+static bool rewriteExecuteInlineRange(Method* method, u2* insns,
+ MethodType methodType, const InlineSub* inlineSubs)
+{
+ ClassObject* clazz = method->clazz;
+ Method* calledMethod;
+ u2 methodIdx = insns[1];
+
+ calledMethod = dvmOptResolveMethod(clazz, methodIdx, methodType, NULL);
+ if (calledMethod == NULL) {
+ LOGV("+++ DexOpt inline/range: can't find %d\n", methodIdx);
+ return false;
+ }
+
+ while (inlineSubs->method != NULL) {
+ if (inlineSubs->method == calledMethod) {
+ assert((insns[0] & 0xff) == OP_INVOKE_DIRECT_RANGE ||
+ (insns[0] & 0xff) == OP_INVOKE_STATIC_RANGE ||
+ (insns[0] & 0xff) == OP_INVOKE_VIRTUAL_RANGE);
+ insns[0] = (insns[0] & 0xff00) | (u2) OP_EXECUTE_INLINE_RANGE;
+ insns[1] = (u2) inlineSubs->inlineIdx;
+
+ //LOGI("DexOpt: execute-inline/range %s.%s --> %s.%s\n",
+ // method->clazz->descriptor, method->name,
+ // calledMethod->clazz->descriptor, calledMethod->name);
+ return true;
+ }
+
+ inlineSubs++;
+ }
+
+ return false;
+}
+
diff --git a/vm/analysis/Optimize.h b/vm/analysis/Optimize.h
new file mode 100644
index 0000000..eb47793
--- /dev/null
+++ b/vm/analysis/Optimize.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Bytecode optimization declarations.
+ */
+#ifndef _DALVIK_OPTIMIZE
+#define _DALVIK_OPTIMIZE
+
+/*
+ * Entry point from DEX preparation.
+ */
+bool dvmRewriteDex(u1* addr, int len, bool doVerify, bool doOpt,
+ u4* pHeaderFlags, DexClassLookup** ppClassLookup);
+
+/*
+ * Abbreviated resolution functions, for use by optimization and verification
+ * code.
+ */
+ClassObject* dvmOptResolveClass(ClassObject* referrer, u4 classIdx,
+ VerifyError* pFailure);
+Method* dvmOptResolveMethod(ClassObject* referrer, u4 methodIdx,
+ MethodType methodType, VerifyError* pFailure);
+Method* dvmOptResolveInterfaceMethod(ClassObject* referrer, u4 methodIdx);
+InstField* dvmOptResolveInstField(ClassObject* referrer, u4 ifieldIdx,
+ VerifyError* pFailure);
+StaticField* dvmOptResolveStaticField(ClassObject* referrer, u4 sfieldIdx,
+ VerifyError* pFailure);
+
+#endif /*_DALVIK_OPTIMIZE*/
diff --git a/vm/analysis/VerifySubs.h b/vm/analysis/VerifySubs.h
index a87c6f1..9e05e9d 100644
--- a/vm/analysis/VerifySubs.h
+++ b/vm/analysis/VerifySubs.h
@@ -22,11 +22,13 @@
/*
* InsnFlags is a 32-bit integer with the following layout:
- * 0-15 instruction length (or 0 if this address doesn't hold an opcode)
- * 16 opcode flag (indicating this address holds an opcode)
- * 17 try block (indicating exceptions thrown here may be caught locally)
- * 30 visited (verifier has examined this instruction at least once)
- * 31 changed (set/cleared as bytecode verifier runs)
+ * 0-15 instruction length (or 0 if this address doesn't hold an opcode)
+ * 16-31 single bit flags:
+ * InTry: in "try" block; exceptions thrown here may be caught locally
+ * BranchTarget: other instructions can branch to this instruction
+ * GcPoint: this instruction is a GC safe point
+ * Visited: verifier has examined this instruction at least once
+ * Changed: set/cleared as bytecode verifier runs
*/
typedef u4 InsnFlags;
diff --git a/vm/compiler/Compiler.c b/vm/compiler/Compiler.c
index 6cb1c77..9964285 100644
--- a/vm/compiler/Compiler.c
+++ b/vm/compiler/Compiler.c
@@ -590,7 +590,7 @@
* of the vm but this should be acceptable.
*/
if (!gDvmJit.blockingMode)
- dvmCheckSuspendPending(NULL);
+ dvmCheckSuspendPending(dvmThreadSelf());
/* Is JitTable filling up? */
if (gDvmJit.jitTableEntriesUsed >
(gDvmJit.jitTableSize - gDvmJit.jitTableSize/4)) {