blob: 8d73604e00010096f207179683fcdcb553873d02 [file] [log] [blame]
/*
* Copyright (C) 2007 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.
*/
package dalvik.system;
import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.compat.annotation.UnsupportedAppUsage;
import android.system.ErrnoException;
import dalvik.annotation.compat.VersionCodes;
import dalvik.annotation.optimization.ReachabilitySensitive;
import libcore.io.Libcore;
import libcore.util.NonNull;
import libcore.util.Nullable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
/**
* Loads DEX files. This class is meant for internal use and should not be used by applications.
*
* Applications should not instantiate this class. It will hurt performance in most cases and will
* lead to incorrect execution of bytecode in the worst case. Applications should use one of the
* standard classloaders such as {@link dalvik.system.PathClassLoader} instead. <b>Non-static APIs
* will be removed in a future Android release</b>.
*/
public final class DexFile {
/**
* If close is called, mCookie becomes null but the internal cookie is preserved if the close
* failed so that we can free resources in the finalizer.
*/
@UnsupportedAppUsage
@ReachabilitySensitive
private Object mCookie;
@UnsupportedAppUsage
private Object mInternalCookie;
@UnsupportedAppUsage
private final String mFileName;
/**
* Enforce the file passed to open DexFile to be set as read-only for apps targeting U+. This
* is to prevent files to be dynamically loaded being unexpectedly overwritten by
* malicious actors.
*
* @hide
*/
// TODO (topjohnwu@): change to @EnabledSince with U API version
@TestApi
@ChangeId
@EnabledAfter(targetSdkVersion = VersionCodes.TIRAMISU)
public static final long ENFORCE_READ_ONLY_JAVA_DCL = 218865702;
/**
* Opens a DEX file from a given File object.
*
* @deprecated Applications should use one of the standard classloaders such
* as {@link dalvik.system.PathClassLoader} instead. <b>This API will be removed
* in a future Android release</b>.
*/
@Deprecated
public DexFile(File file) throws IOException {
this(file.getPath());
}
/*
* Private version with class loader argument.
*
* @param file
* the File object referencing the actual DEX file
* @param loader
* the class loader object creating the DEX file object
* @param elements
* the temporary dex path list elements from DexPathList.makeElements
*/
DexFile(File file, ClassLoader loader, DexPathList.Element[] elements)
throws IOException {
this(file.getPath(), loader, elements);
}
/**
* Opens a DEX file from a given filename.
*
* @deprecated Applications should use one of the standard classloaders such
* as {@link dalvik.system.PathClassLoader} instead. <b>This API will be removed
* in a future Android release</b>.
*/
@Deprecated
public DexFile(String fileName) throws IOException {
this(fileName, null, null);
}
/*
* Private version with class loader argument.
*
* @param fileName
* the filename of the DEX file
* @param loader
* the class loader creating the DEX file object
* @param elements
* the temporary dex path list elements from DexPathList.makeElements
*/
DexFile(String fileName, ClassLoader loader, DexPathList.Element[] elements)
throws IOException {
mCookie = openDexFile(fileName, null, 0, loader, elements);
mInternalCookie = mCookie;
mFileName = fileName;
//System.out.println("DEX FILE cookie is " + mCookie + " fileName=" + fileName);
}
DexFile(ByteBuffer[] bufs, ClassLoader loader, DexPathList.Element[] elements)
throws IOException {
mCookie = openInMemoryDexFiles(bufs, loader, elements);
mInternalCookie = mCookie;
mFileName = null;
}
/**
* Opens a DEX file from a given filename, using a specified file
* to hold the optimized data.
*
* @param sourceName
* Jar or APK file with "classes.dex".
* @param outputName
* File that will hold the optimized form of the DEX data.
* @param flags
* Enable optional features.
* @param loader
* The class loader creating the DEX file object.
* @param elements
* The temporary dex path list elements from DexPathList.makeElements
*/
private DexFile(String sourceName, String outputName, int flags, ClassLoader loader,
DexPathList.Element[] elements) throws IOException {
if (outputName != null) {
try {
String parent = new File(outputName).getParent();
if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {
throw new IllegalArgumentException("Optimized data directory " + parent
+ " is not owned by the current user. Shared storage cannot protect"
+ " your application from code injection attacks.");
}
} catch (ErrnoException ignored) {
// assume we'll fail with a more contextual error later
}
}
mCookie = openDexFile(sourceName, outputName, flags, loader, elements);
mInternalCookie = mCookie;
mFileName = sourceName;
//System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName);
}
/**
* Open a DEX file, specifying the file in which the optimized DEX
* data should be written. If the optimized form exists and appears
* to be current, it will be used; if not, the VM will attempt to
* regenerate it.
*
* @deprecated Applications should use one of the standard classloaders such
* as {@link dalvik.system.PathClassLoader} instead. <b>This API will be removed
* in a future Android release</b>.
*/
@Deprecated
static public DexFile loadDex(String sourcePathName, String outputPathName,
int flags) throws IOException {
/*
* TODO: we may want to cache previously-opened DexFile objects.
* The cache would be synchronized with close(). This would help
* us avoid mapping the same DEX more than once when an app
* decided to open it multiple times. In practice this may not
* be a real issue.
*/
return loadDex(sourcePathName, outputPathName, flags, null, null);
}
/*
* Private version of loadDex that also takes a class loader.
*
* @param sourcePathName
* Jar or APK file with "classes.dex". (May expand this to include
* "raw DEX" in the future.)
* @param outputPathName
* File that will hold the optimized form of the DEX data.
* @param flags
* Enable optional features. (Currently none defined.)
* @param loader
* Class loader that is aloading the DEX file.
* @param elements
* The temporary dex path list elements from DexPathList.makeElements
* @return
* A new or previously-opened DexFile.
* @throws IOException
* If unable to open the source or output file.
*/
@UnsupportedAppUsage
static DexFile loadDex(String sourcePathName, String outputPathName,
int flags, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
/*
* TODO: we may want to cache previously-opened DexFile objects.
* The cache would be synchronized with close(). This would help
* us avoid mapping the same DEX more than once when an app
* decided to open it multiple times. In practice this may not
* be a real issue.
*/
return new DexFile(sourcePathName, outputPathName, flags, loader, elements);
}
/**
* Gets the name of the (already opened) DEX file.
*
* @return the file name
*
* @deprecated Applications should use one of the standard classloaders such
* as {@link dalvik.system.PathClassLoader} instead. <b>This API will be removed
* in a future Android release</b>.
*/
@Deprecated
public String getName() {
return mFileName;
}
@Override public String toString() {
if (mFileName != null) {
return getName();
} else {
return "InMemoryDexFile[cookie=" + Arrays.toString((long[]) mCookie) + "]";
}
}
/**
* Closes the DEX file.
* <p>
* This may not be able to release all of the resources. If classes from this DEX file are
* still resident, the DEX file can't be unmapped. In the case where we do not release all
* the resources, close is called again in the finalizer.
*
* @throws IOException
* if an I/O error occurs during closing the file, which
* normally should not happen
*
* @deprecated Applications should use one of the standard classloaders such
* as {@link dalvik.system.PathClassLoader} instead. <b>This API will be removed
* in a future Android release</b>.
*/
@Deprecated
public void close() throws IOException {
if (mInternalCookie != null) {
if (closeDexFile(mInternalCookie)) {
mInternalCookie = null;
}
mCookie = null;
}
}
/**
* Loads a class. Returns the class on success, or a {@code null} reference
* on failure.
* <p>
* If you are not calling this from a class loader, this is most likely not
* going to do what you want. Use {@link Class#forName(String)} instead.
* <p>
* The method does not throw {@link ClassNotFoundException} if the class
* isn't found because it isn't reasonable to throw exceptions wildly every
* time a class is not found in the first DEX file we look at.
*
* @param name
* the class name, which should look like "java/lang/String"
*
* @param loader
* the class loader that tries to load the class (in most cases
* the caller of the method
*
* @return the {@link Class} object representing the class, or {@code null}
* if the class cannot be loaded
*
* @deprecated Applications should use one of the standard classloaders such
* as {@link dalvik.system.PathClassLoader} instead. <b>This API will be removed
* in a future Android release</b>.
*/
@Deprecated
public Class loadClass(String name, ClassLoader loader) {
String slashName = name.replace('.', '/');
return loadClassBinaryName(slashName, loader, null);
}
/**
* See {@link #loadClass(String, ClassLoader)}.
*
* This takes a "binary" class name to better match ClassLoader semantics.
*
* @hide
*/
@UnsupportedAppUsage
public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
return defineClass(name, loader, mCookie, this, suppressed);
}
private static Class defineClass(String name, ClassLoader loader, Object cookie,
DexFile dexFile, List<Throwable> suppressed) {
Class result = null;
try {
result = defineClassNative(name, loader, cookie, dexFile);
} catch (NoClassDefFoundError e) {
if (suppressed != null) {
suppressed.add(e);
}
} catch (ClassNotFoundException e) {
if (suppressed != null) {
suppressed.add(e);
}
}
return result;
}
/**
* Enumerate the names of the classes in this DEX file.
*
* @return an enumeration of names of classes contained in the DEX file, in
* the usual internal form (like "java/lang/String").
*
* @deprecated Applications should use one of the standard classloaders such
* as {@link dalvik.system.PathClassLoader} instead. <b>This API will be removed
* in a future Android release</b>.
*/
@Deprecated
public Enumeration<String> entries() {
return new DFEnum(this);
}
/*
* Helper class.
*/
private static class DFEnum implements Enumeration<String> {
private int mIndex;
@UnsupportedAppUsage
private String[] mNameList;
DFEnum(DexFile df) {
mIndex = 0;
mNameList = getClassNameList(df.mCookie);
}
public boolean hasMoreElements() {
return (mIndex < mNameList.length);
}
public String nextElement() {
return mNameList[mIndex++];
}
}
/**
* Called when the class is finalized. Makes sure the DEX file is closed.
*
* @throws IOException
* if an I/O error occurs during closing the file, which
* normally should not happen
*/
@Override protected void finalize() throws Throwable {
try {
if (mInternalCookie != null && !closeDexFile(mInternalCookie)) {
throw new AssertionError("Failed to close dex file in finalizer.");
}
mInternalCookie = null;
mCookie = null;
} finally {
super.finalize();
}
}
/*
* Open a DEX file. The value returned is a magic VM cookie. On
* failure, an IOException is thrown.
*/
@UnsupportedAppUsage
private static Object openDexFile(String sourceName, String outputName, int flags,
ClassLoader loader, DexPathList.Element[] elements) throws IOException {
// Use absolute paths to enable the use of relative paths when testing on host.
return openDexFileNative(new File(sourceName).getAbsolutePath(),
(outputName == null)
? null
: new File(outputName).getAbsolutePath(),
flags,
loader,
elements);
}
private static Object openInMemoryDexFiles(ByteBuffer[] bufs, ClassLoader loader,
DexPathList.Element[] elements) throws IOException {
// Preprocess the ByteBuffers for openInMemoryDexFilesNative. We extract
// the backing array (non-direct buffers only) and start/end positions
// so that the native method does not have to call Java methods anymore.
byte[][] arrays = new byte[bufs.length][];
int[] starts = new int[bufs.length];
int[] ends = new int[bufs.length];
for (int i = 0; i < bufs.length; ++i) {
arrays[i] = bufs[i].isDirect() ? null : bufs[i].array();
starts[i] = bufs[i].position();
ends[i] = bufs[i].limit();
}
return openInMemoryDexFilesNative(bufs, arrays, starts, ends, loader, elements);
}
private static native Object openInMemoryDexFilesNative(ByteBuffer[] bufs, byte[][] arrays,
int[] starts, int[] ends, ClassLoader loader, DexPathList.Element[] elements);
/*
* Initiates background verification of this DexFile. This is a sepearate down-call
* from openDexFile and openInMemoryDexFiles because it requires the class loader's
* DexPathList to have been initialized for its classes to be resolvable by ART.
* DexPathList will open the dex files first, finalize `dexElements` and then call this.
*/
/*package*/ void verifyInBackground(ClassLoader classLoader) {
verifyInBackgroundNative(mCookie, classLoader);
}
private static native void verifyInBackgroundNative(Object mCookie, ClassLoader classLoader);
/*
* Returns true if the dex file is backed by a valid oat file.
*/
@UnsupportedAppUsage
/*package*/ boolean isBackedByOatFile() {
return isBackedByOatFile(mCookie);
}
/*
* Set the dex file as trusted: it can access hidden APIs of the platform.
*/
/*package*/ void setTrusted() {
setTrusted(mCookie);
}
/*
* Returns true if we managed to close the dex file.
*/
private static native boolean closeDexFile(Object cookie);
private static native Class defineClassNative(String name, ClassLoader loader, Object cookie,
DexFile dexFile)
throws ClassNotFoundException, NoClassDefFoundError;
@UnsupportedAppUsage
private static native String[] getClassNameList(Object cookie);
private static native boolean isBackedByOatFile(Object cookie);
private static native void setTrusted(Object cookie);
/*
* Open a DEX file. The value returned is a magic VM cookie. On
* failure, an IOException is thrown.
*/
@UnsupportedAppUsage
private static native Object openDexFileNative(String sourceName, String outputName, int flags,
ClassLoader loader, DexPathList.Element[] elements);
/**
* Returns true if the VM believes that the apk/jar file is out of date
* and should be passed through "dexopt" again.
*
* @param fileName the absolute path to the apk/jar file to examine.
* @return true if dexopt should be called on the file, false otherwise.
* @throws java.io.FileNotFoundException if fileName is not readable,
* not a file, or not present.
* @throws java.io.IOException if fileName is not a valid apk/jar file or
* if problems occur while parsing it.
* @throws java.lang.NullPointerException if fileName is null.
*
* @deprecated Use {@code Artd.getDexoptNeeded} instead.
*/
@Deprecated
public static native boolean isDexOptNeeded(String fileName)
throws FileNotFoundException, IOException;
/**
* No dexopt should (or can) be done to update the apk/jar.
*
* See {@link #getDexOptNeeded(String, String, String, boolean, boolean)}.
*
* @hide
*/
@SystemApi(client = MODULE_LIBRARIES)
@Deprecated
public static final int NO_DEXOPT_NEEDED = 0;
/**
* dex2oat should be run to update the apk/jar from scratch.
*
* See {@link #getDexOptNeeded(String, String, String, boolean, boolean)}.
*
* @hide
*/
@Deprecated
public static final int DEX2OAT_FROM_SCRATCH = 1;
/**
* dex2oat should be run to update the apk/jar because the existing code
* is out of date with respect to the boot image.
*
* See {@link #getDexOptNeeded(String, String, String, boolean, boolean)}.
*
* @hide
*/
@Deprecated
public static final int DEX2OAT_FOR_BOOT_IMAGE = 2;
/**
* dex2oat should be run to update the apk/jar because the existing code
* is out of date with respect to the target compiler filter.
*
* See {@link #getDexOptNeeded(String, String, String, boolean, boolean)}.
*
* @hide
*/
@SystemApi(client = MODULE_LIBRARIES)
@Deprecated
public static final int DEX2OAT_FOR_FILTER = 3;
/**
* Calls {@link #getDexOptNeeded(String, String, String, String, String, boolean, boolean)}
* with a null class loader context.
*
* TODO(ngeoffray, calin): deprecate / remove.
* @hide
*/
public static int getDexOptNeeded(String fileName,
String instructionSet, String compilerFilter, boolean newProfile, boolean downgrade)
throws FileNotFoundException, IOException {
return getDexOptNeeded(
fileName, instructionSet, compilerFilter, null, newProfile, downgrade);
}
/**
* Returns the VM's opinion of what kind of dexopt is needed to make the
* apk/jar file up to date, where {@code targetMode} is used to indicate what
* type of compilation the caller considers up-to-date, and {@code newProfile}
* is used to indicate whether profile information has changed recently.
*
* @param fileName the absolute path to the apk/jar file to examine.
* @param instructionSet instruction set to examine
* @param compilerFilter a compiler filter to use for what a caller considers up-to-date.
* @param classLoaderContext a string encoding the class loader context the dex file
* is intended to have at runtime.
* @param newProfile flag that describes whether a profile corresponding
* to the dex file has been recently updated and should be considered
* in the state of the file.
* @param downgrade flag that describes if the purpose of dexopt is to downgrade the
* compiler filter. If set to false, will be evaluated as an upgrade request.
* @return NO_DEXOPT_NEEDED, or DEX2OAT_*. See documentation
* of the particular status code for more information on its
* meaning. Returns a positive status code if the status refers to
* the oat file in the oat location. Returns a negative status
* code if the status refers to the oat file in the odex location.
* @throws java.io.FileNotFoundException if fileName is not readable,
* not a file, or not present.
* @throws java.io.IOException if fileName is not a valid apk/jar file or
* if problems occur while parsing it.
* @throws java.lang.NullPointerException if {@code fileName} is {@code null}.
* @deprecated Use {@code Artd.getDexoptNeeded} instead.
*
* @hide
*/
@SystemApi(client = MODULE_LIBRARIES)
@Deprecated
public static native int getDexOptNeeded(@NonNull String fileName,
@NonNull String instructionSet, @NonNull String compilerFilter, @Nullable String classLoaderContext,
boolean newProfile, boolean downgrade)
throws FileNotFoundException, IOException;
/**
* Returns the status of the dex file {@code fileName}. The returned string is
* an opaque, human readable representation of the current status. The output
* is only meant for debugging and is not guaranteed to be stable across
* releases and/or devices.
*
* @hide
*/
public static native String getDexFileStatus(String fileName, String instructionSet)
throws FileNotFoundException;
/**
* Encapsulates information about the optimizations performed on a dex file.
*
* Note that the info is only meant for debugging and is not guaranteed to be
* stable across releases and/or devices.
*/
public static final class OptimizationInfo {
// The human readable refined optimization status of the validity of the odex file.
private final String status;
// The optimization reason. The reason might be "unknown" if the
// the compiler artifacts were not annotated during optimizations.
private final String reason;
private OptimizationInfo(String status, String reason) {
this.status = status;
this.reason = reason;
}
/**
* Returns the human readable refined status of the validity of the odex file.
*
* @return optimization status
*
* @hide
*/
@SystemApi(client = MODULE_LIBRARIES)
public @NonNull String getStatus() {
return status;
}
/**
* Returns the reason of a particular optimization used.
*
* @return optimization reason
*
* @hide
*/
@SystemApi(client = MODULE_LIBRARIES)
public @NonNull String getReason() {
return reason;
}
/**
* Returns whether the dex file is verified.
*/
public boolean isVerified() {
return isVerifiedCompilerFilter(status);
}
/**
* Returns whether the dex file is in an optimal state. Currently, this means the dex file
* is either ahead-of-time compiled with a profile or fully ahead-of-time compiled.
*/
public boolean isOptimized() {
return isOptimizedCompilerFilter(status);
}
/**
* Returns whether the dex file is fully ahead-of-time compiled.
*/
public boolean isFullyCompiled() {
return isOptimizedCompilerFilter(status) && !isProfileGuidedCompilerFilter(status);
}
}
/**
* Retrieves the optimization info for a dex file.
*
* @param fileName path to dex file
* @param instructionSet instruction set to get optimization info for
* @return {@link OptimizationInfo} for {@code fileName} dex file
* @throws FileNotFoundException if {@code fileName} not found
* @deprecated Applications should use {@link VMRuntime#getCurrentOptimizationStatus()}.
* System server should use {@code ArtManagerLocal.getOptimizationStatus}.
*
* @hide
*/
@SystemApi(client = MODULE_LIBRARIES)
@Deprecated
public static @NonNull OptimizationInfo getDexFileOptimizationInfo(
@NonNull String fileName, @NonNull String instructionSet) throws FileNotFoundException {
String[] status = getDexFileOptimizationStatus(fileName, instructionSet);
return new OptimizationInfo(status[0], status[1]);
}
/**
* Returns the optimization status of the dex file {@code fileName}. The returned
* array will have 2 elements which specify:
* - index 0: the level of optimizations
* - index 1: the optimization reason. The reason might be "unknown" if the
* the compiler artifacts were not annotated during optimizations.
*
* The output is only meant for debugging and is not guaranteed to be stable across
* releases and/or devices.
*
* @hide
*/
private static native String[] getDexFileOptimizationStatus(
String fileName, String instructionSet) throws FileNotFoundException;
/**
* Returns the paths of the optimized files generated for {@code fileName}.
* If no optimized code exists the method returns {@code null}.
*
* @param fileName path to dex file
* @param instructionSet instruction set to get optimized files for
* @return paths to optimized code, or {@code null} if they do not exist
* @throws FileNotFoundException
*
* @hide
*/
@SystemApi(client = MODULE_LIBRARIES)
public static native @Nullable String[] getDexFileOutputPaths(@NonNull String fileName, @NonNull String instructionSet)
throws FileNotFoundException;
/**
* Returns whether the given filter is a valid filter.
*
* @param filter filter string
* @return whether given filter string is a valid filter
*
* @hide
*/
@SystemApi(client = MODULE_LIBRARIES)
public native static boolean isValidCompilerFilter(@NonNull String filter);
/**
* Returns whether the given filter is based on profiles.
*
* @param filter filter string
* @return whether given filter string is based on profiles
*
* @hide
*/
@SystemApi(client = MODULE_LIBRARIES)
public native static boolean isProfileGuidedCompilerFilter(@NonNull String filter);
/**
* Returns whether the given filter includes verification.
*
* @hide
*/
@SystemApi(client = MODULE_LIBRARIES)
public native static boolean isVerifiedCompilerFilter(@NonNull String filter);
/**
* Returns whether the given filter includes AOT compilation.
*
* @hide
*/
@SystemApi(client = MODULE_LIBRARIES)
public native static boolean isOptimizedCompilerFilter(@NonNull String filter);
/**
* Returns whether JAR/DEX files' read-only status is enforced.
*
* @see #ENFORCE_READ_ONLY_JAVA_DCL
* @hide
*/
@TestApi
public static native boolean isReadOnlyJavaDclEnforced();
/**
* Returns the version of the compiler filter that is not based on profiles.
* If the input is not a valid filter, or the filter is already not based on
* profiles, this returns the input.
*
* @param filter filter string
* @return version of the compiler filter that is not based on profiles
*
* @hide
*/
public native static String getNonProfileGuidedCompilerFilter(String filter);
/**
* Returns the version of the compiler filter that is suitable for safe mode.
* If the input is not a valid filter, or the filter is already suitable for
* safe mode, this returns the input.
*
* @param filter filter string
* @return version of the compiler filter that is suitable for safe mode
*
* @hide
*/
@SystemApi(client = MODULE_LIBRARIES)
public native static @NonNull String getSafeModeCompilerFilter(@NonNull String filter);
/**
* Returns the static file size of the original dex file.
* The original size of the uncompressed dex file is returned.
* On device the dex file may be compressed or embedded in some other
* file (e.g. oat) in a platform implementation dependent manner. This
* method abstracts away from those details and provides an efficient
* implementation given that the dex file in question has already been
* uncompressed, extracted, and/or loaded by the runtime as appropriate.
* <p>
* In the case of multidex, returns the sum of the original uncompressed
* multidex entry file sizes.
*
* @hide
*/
public long getStaticSizeOfDexFile() {
return getStaticSizeOfDexFile(mCookie);
}
private static native long getStaticSizeOfDexFile(Object cookie);
}