1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package dalvik.system;
18 
19 import android.system.ErrnoException;
20 import android.system.StructStat;
21 import java.io.File;
22 import java.io.FileNotFoundException;
23 import java.io.IOException;
24 import java.util.ArrayList;
25 import java.util.Enumeration;
26 import java.util.List;
27 import libcore.io.Libcore;
28 
29 /**
30  * Manipulates DEX files. The class is similar in principle to
31  * {@link java.util.zip.ZipFile}. It is used primarily by class loaders.
32  * <p>
33  * Note we don't directly open and read the DEX file here. They're memory-mapped
34  * read-only by the VM.
35  */
36 public final class DexFile {
37   /**
38    * If close is called, mCookie becomes null but the internal cookie is preserved if the close
39    * failed so that we can free resources in the finalizer.
40    */
41     private Object mCookie;
42     private Object mInternalCookie;
43     private final String mFileName;
44     private final CloseGuard guard = CloseGuard.get();
45 
46     /**
47      * Opens a DEX file from a given File object. This will usually be a ZIP/JAR
48      * file with a "classes.dex" inside.
49      *
50      * The VM will generate the name of the corresponding file in
51      * /data/dalvik-cache and open it, possibly creating or updating
52      * it first if system permissions allow.  Don't pass in the name of
53      * a file in /data/dalvik-cache, as the named file is expected to be
54      * in its original (pre-dexopt) state.
55      *
56      * @param file
57      *            the File object referencing the actual DEX file
58      *
59      * @throws IOException
60      *             if an I/O error occurs, such as the file not being found or
61      *             access rights missing for opening it
62      */
DexFile(File file)63     public DexFile(File file) throws IOException {
64         this(file.getPath());
65     }
66     /*
67      * Private version with class loader argument.
68      *
69      * @param file
70      *            the File object referencing the actual DEX file
71      * @param loader
72      *            the class loader object creating the DEX file object
73      * @param elements
74      *            the temporary dex path list elements from DexPathList.makeElements
75      */
DexFile(File file, ClassLoader loader, DexPathList.Element[] elements)76     DexFile(File file, ClassLoader loader, DexPathList.Element[] elements)
77             throws IOException {
78         this(file.getPath(), loader, elements);
79     }
80 
81     /**
82      * Opens a DEX file from a given filename. This will usually be a ZIP/JAR
83      * file with a "classes.dex" inside.
84      *
85      * The VM will generate the name of the corresponding file in
86      * /data/dalvik-cache and open it, possibly creating or updating
87      * it first if system permissions allow.  Don't pass in the name of
88      * a file in /data/dalvik-cache, as the named file is expected to be
89      * in its original (pre-dexopt) state.
90      *
91      * @param fileName
92      *            the filename of the DEX file
93      *
94      * @throws IOException
95      *             if an I/O error occurs, such as the file not being found or
96      *             access rights missing for opening it
97      */
DexFile(String fileName)98     public DexFile(String fileName) throws IOException {
99         this(fileName, null, null);
100     }
101 
102     /*
103      * Private version with class loader argument.
104      *
105      * @param fileName
106      *            the filename of the DEX file
107      * @param loader
108      *            the class loader creating the DEX file object
109      * @param elements
110      *            the temporary dex path list elements from DexPathList.makeElements
111      */
DexFile(String fileName, ClassLoader loader, DexPathList.Element[] elements)112     DexFile(String fileName, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
113         mCookie = openDexFile(fileName, null, 0, loader, elements);
114         mInternalCookie = mCookie;
115         mFileName = fileName;
116         guard.open("close");
117         //System.out.println("DEX FILE cookie is " + mCookie + " fileName=" + fileName);
118     }
119 
120     /**
121      * Opens a DEX file from a given filename, using a specified file
122      * to hold the optimized data.
123      *
124      * @param sourceName
125      *  Jar or APK file with "classes.dex".
126      * @param outputName
127      *  File that will hold the optimized form of the DEX data.
128      * @param flags
129      *  Enable optional features.
130      * @param loader
131      *  The class loader creating the DEX file object.
132      * @param elements
133      *  The temporary dex path list elements from DexPathList.makeElements
134      */
DexFile(String sourceName, String outputName, int flags, ClassLoader loader, DexPathList.Element[] elements)135     private DexFile(String sourceName, String outputName, int flags, ClassLoader loader,
136             DexPathList.Element[] elements) throws IOException {
137         if (outputName != null) {
138             try {
139                 String parent = new File(outputName).getParent();
140                 if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {
141                     throw new IllegalArgumentException("Optimized data directory " + parent
142                             + " is not owned by the current user. Shared storage cannot protect"
143                             + " your application from code injection attacks.");
144                 }
145             } catch (ErrnoException ignored) {
146                 // assume we'll fail with a more contextual error later
147             }
148         }
149 
150         mCookie = openDexFile(sourceName, outputName, flags, loader, elements);
151         mFileName = sourceName;
152         //System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName);
153     }
154 
155     /**
156      * Open a DEX file, specifying the file in which the optimized DEX
157      * data should be written.  If the optimized form exists and appears
158      * to be current, it will be used; if not, the VM will attempt to
159      * regenerate it.
160      *
161      * This is intended for use by applications that wish to download
162      * and execute DEX files outside the usual application installation
163      * mechanism.  This function should not be called directly by an
164      * application; instead, use a class loader such as
165      * dalvik.system.DexClassLoader.
166      *
167      * @param sourcePathName
168      *  Jar or APK file with "classes.dex".  (May expand this to include
169      *  "raw DEX" in the future.)
170      * @param outputPathName
171      *  File that will hold the optimized form of the DEX data.
172      * @param flags
173      *  Enable optional features.  (Currently none defined.)
174      * @return
175      *  A new or previously-opened DexFile.
176      * @throws IOException
177      *  If unable to open the source or output file.
178      */
loadDex(String sourcePathName, String outputPathName, int flags)179     static public DexFile loadDex(String sourcePathName, String outputPathName,
180         int flags) throws IOException {
181 
182         /*
183          * TODO: we may want to cache previously-opened DexFile objects.
184          * The cache would be synchronized with close().  This would help
185          * us avoid mapping the same DEX more than once when an app
186          * decided to open it multiple times.  In practice this may not
187          * be a real issue.
188          */
189         return loadDex(sourcePathName, outputPathName, flags, null, null);
190     }
191 
192     /*
193      * Private version of loadDex that also takes a class loader.
194      *
195      * @param sourcePathName
196      *  Jar or APK file with "classes.dex".  (May expand this to include
197      *  "raw DEX" in the future.)
198      * @param outputPathName
199      *  File that will hold the optimized form of the DEX data.
200      * @param flags
201      *  Enable optional features.  (Currently none defined.)
202      * @param loader
203      *  Class loader that is aloading the DEX file.
204      * @param elements
205      *  The temporary dex path list elements from DexPathList.makeElements
206      * @return
207      *  A new or previously-opened DexFile.
208      * @throws IOException
209      *  If unable to open the source or output file.
210      */
loadDex(String sourcePathName, String outputPathName, int flags, ClassLoader loader, DexPathList.Element[] elements)211     static DexFile loadDex(String sourcePathName, String outputPathName,
212         int flags, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
213 
214         /*
215          * TODO: we may want to cache previously-opened DexFile objects.
216          * The cache would be synchronized with close().  This would help
217          * us avoid mapping the same DEX more than once when an app
218          * decided to open it multiple times.  In practice this may not
219          * be a real issue.
220          */
221         return new DexFile(sourcePathName, outputPathName, flags, loader, elements);
222     }
223 
224     /**
225      * Gets the name of the (already opened) DEX file.
226      *
227      * @return the file name
228      */
getName()229     public String getName() {
230         return mFileName;
231     }
232 
toString()233     @Override public String toString() {
234         return getName();
235     }
236 
237     /**
238      * Closes the DEX file.
239      * <p>
240      * This may not be able to release all of the resources. If classes from this DEX file are
241      * still resident, the DEX file can't be unmapped. In the case where we do not release all
242      * the resources, close is called again in the finalizer.
243      *
244      * @throws IOException
245      *             if an I/O error occurs during closing the file, which
246      *             normally should not happen
247      */
close()248     public void close() throws IOException {
249         if (mInternalCookie != null) {
250             if (closeDexFile(mInternalCookie)) {
251                 mInternalCookie = null;
252             }
253             guard.close();
254             mCookie = null;
255         }
256     }
257 
258     /**
259      * Loads a class. Returns the class on success, or a {@code null} reference
260      * on failure.
261      * <p>
262      * If you are not calling this from a class loader, this is most likely not
263      * going to do what you want. Use {@link Class#forName(String)} instead.
264      * <p>
265      * The method does not throw {@link ClassNotFoundException} if the class
266      * isn't found because it isn't reasonable to throw exceptions wildly every
267      * time a class is not found in the first DEX file we look at.
268      *
269      * @param name
270      *            the class name, which should look like "java/lang/String"
271      *
272      * @param loader
273      *            the class loader that tries to load the class (in most cases
274      *            the caller of the method
275      *
276      * @return the {@link Class} object representing the class, or {@code null}
277      *         if the class cannot be loaded
278      */
loadClass(String name, ClassLoader loader)279     public Class loadClass(String name, ClassLoader loader) {
280         String slashName = name.replace('.', '/');
281         return loadClassBinaryName(slashName, loader, null);
282     }
283 
284     /**
285      * See {@link #loadClass(String, ClassLoader)}.
286      *
287      * This takes a "binary" class name to better match ClassLoader semantics.
288      *
289      * @hide
290      */
loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed)291     public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
292         return defineClass(name, loader, mCookie, this, suppressed);
293     }
294 
defineClass(String name, ClassLoader loader, Object cookie, DexFile dexFile, List<Throwable> suppressed)295     private static Class defineClass(String name, ClassLoader loader, Object cookie,
296                                      DexFile dexFile, List<Throwable> suppressed) {
297         Class result = null;
298         try {
299             result = defineClassNative(name, loader, cookie, dexFile);
300         } catch (NoClassDefFoundError e) {
301             if (suppressed != null) {
302                 suppressed.add(e);
303             }
304         } catch (ClassNotFoundException e) {
305             if (suppressed != null) {
306                 suppressed.add(e);
307             }
308         }
309         return result;
310     }
311 
312     /**
313      * Enumerate the names of the classes in this DEX file.
314      *
315      * @return an enumeration of names of classes contained in the DEX file, in
316      *         the usual internal form (like "java/lang/String").
317      */
entries()318     public Enumeration<String> entries() {
319         return new DFEnum(this);
320     }
321 
322     /*
323      * Helper class.
324      */
325     private class DFEnum implements Enumeration<String> {
326         private int mIndex;
327         private String[] mNameList;
328 
DFEnum(DexFile df)329         DFEnum(DexFile df) {
330             mIndex = 0;
331             mNameList = getClassNameList(mCookie);
332         }
333 
hasMoreElements()334         public boolean hasMoreElements() {
335             return (mIndex < mNameList.length);
336         }
337 
nextElement()338         public String nextElement() {
339             return mNameList[mIndex++];
340         }
341     }
342 
343     /**
344      * Called when the class is finalized. Makes sure the DEX file is closed.
345      *
346      * @throws IOException
347      *             if an I/O error occurs during closing the file, which
348      *             normally should not happen
349      */
finalize()350     @Override protected void finalize() throws Throwable {
351         try {
352             if (guard != null) {
353                 guard.warnIfOpen();
354             }
355             if (mInternalCookie != null && !closeDexFile(mInternalCookie)) {
356                 throw new AssertionError("Failed to close dex file in finalizer.");
357             }
358             mInternalCookie = null;
359             mCookie = null;
360         } finally {
361             super.finalize();
362         }
363     }
364 
365 
366     /*
367      * Open a DEX file.  The value returned is a magic VM cookie.  On
368      * failure, an IOException is thrown.
369      */
openDexFile(String sourceName, String outputName, int flags, ClassLoader loader, DexPathList.Element[] elements)370     private static Object openDexFile(String sourceName, String outputName, int flags,
371             ClassLoader loader, DexPathList.Element[] elements) throws IOException {
372         // Use absolute paths to enable the use of relative paths when testing on host.
373         return openDexFileNative(new File(sourceName).getAbsolutePath(),
374                                  (outputName == null)
375                                      ? null
376                                      : new File(outputName).getAbsolutePath(),
377                                  flags,
378                                  loader,
379                                  elements);
380     }
381 
382     /*
383      * Returns true if the dex file is backed by a valid oat file.
384      */
isBackedByOatFile()385     /*package*/ boolean isBackedByOatFile() {
386         return isBackedByOatFile(mCookie);
387     }
388 
389     /*
390      * Returns true if we managed to close the dex file.
391      */
closeDexFile(Object cookie)392     private static native boolean closeDexFile(Object cookie);
defineClassNative(String name, ClassLoader loader, Object cookie, DexFile dexFile)393     private static native Class defineClassNative(String name, ClassLoader loader, Object cookie,
394                                                   DexFile dexFile)
395             throws ClassNotFoundException, NoClassDefFoundError;
getClassNameList(Object cookie)396     private static native String[] getClassNameList(Object cookie);
isBackedByOatFile(Object cookie)397     private static native boolean isBackedByOatFile(Object cookie);
398     /*
399      * Open a DEX file.  The value returned is a magic VM cookie.  On
400      * failure, an IOException is thrown.
401      */
openDexFileNative(String sourceName, String outputName, int flags, ClassLoader loader, DexPathList.Element[] elements)402     private static native Object openDexFileNative(String sourceName, String outputName, int flags,
403             ClassLoader loader, DexPathList.Element[] elements);
404 
405     /**
406      * Returns true if the VM believes that the apk/jar file is out of date
407      * and should be passed through "dexopt" again.
408      *
409      * @param fileName the absolute path to the apk/jar file to examine.
410      * @return true if dexopt should be called on the file, false otherwise.
411      * @throws java.io.FileNotFoundException if fileName is not readable,
412      *         not a file, or not present.
413      * @throws java.io.IOException if fileName is not a valid apk/jar file or
414      *         if problems occur while parsing it.
415      * @throws java.lang.NullPointerException if fileName is null.
416      */
isDexOptNeeded(String fileName)417     public static native boolean isDexOptNeeded(String fileName)
418             throws FileNotFoundException, IOException;
419 
420     /**
421      * See {@link #getDexOptNeeded(String, String, int)}.
422      *
423      * @hide
424      */
425     public static final int NO_DEXOPT_NEEDED = 0;
426 
427     /**
428      * See {@link #getDexOptNeeded(String, String, int)}.
429      *
430      * @hide
431      */
432     public static final int DEX2OAT_NEEDED = 1;
433 
434     /**
435      * See {@link #getDexOptNeeded(String, String, int)}.
436      *
437      * @hide
438      */
439     public static final int PATCHOAT_NEEDED = 2;
440 
441     /**
442      * See {@link #getDexOptNeeded(String, String, int)}.
443      *
444      * @hide
445      */
446     public static final int SELF_PATCHOAT_NEEDED = 3;
447 
448     /**
449      * Returns whether the given filter is a valid filter.
450      *
451      * @hide
452      */
isValidCompilerFilter(String filter)453     public native static boolean isValidCompilerFilter(String filter);
454 
455     /**
456      * Returns whether the given filter is based on profiles.
457      *
458      * @hide
459      */
isProfileGuidedCompilerFilter(String filter)460     public native static boolean isProfileGuidedCompilerFilter(String filter);
461 
462     /**
463      * Returns the version of the compiler filter that is not based on profiles.
464      * If the input is not a valid filter, or the filter is already not based on
465      * profiles, this returns the input.
466      *
467      * @hide
468      */
getNonProfileGuidedCompilerFilter(String filter)469     public native static String getNonProfileGuidedCompilerFilter(String filter);
470 
471     /**
472      * Returns the VM's opinion of what kind of dexopt is needed to make the
473      * apk/jar file up to date, where {@code targetMode} is used to indicate what
474      * type of compilation the caller considers up-to-date, and {@code newProfile}
475      * is used to indicate whether profile information has changed recently.
476      *
477      * @param fileName the absolute path to the apk/jar file to examine.
478      * @param compilerFilter a compiler filter to use for what a caller considers up-to-date.
479      * @param newProfile flag that describes whether a profile corresponding
480      *        to the dex file has been recently updated and should be considered
481      *        in the state of the file.
482      * @return NO_DEXOPT_NEEDED if the apk/jar is already up to date.
483      *         DEX2OAT_NEEDED if dex2oat should be called on the apk/jar file.
484      *         PATCHOAT_NEEDED if patchoat should be called on the apk/jar
485      *         file to patch the odex file along side the apk/jar.
486      *         SELF_PATCHOAT_NEEDED if selfpatchoat should be called on the
487      *         apk/jar file to patch the oat file in the dalvik cache.
488      * @throws java.io.FileNotFoundException if fileName is not readable,
489      *         not a file, or not present.
490      * @throws java.io.IOException if fileName is not a valid apk/jar file or
491      *         if problems occur while parsing it.
492      * @throws java.lang.NullPointerException if fileName is null.
493      *
494      * @hide
495      */
getDexOptNeeded(String fileName, String instructionSet, String compilerFilter, boolean newProfile)496     public static native int getDexOptNeeded(String fileName,
497             String instructionSet, String compilerFilter, boolean newProfile)
498             throws FileNotFoundException, IOException;
499 
500     /**
501      * Returns the status of the dex file {@code fileName}. The returned string is
502      * an opaque, human readable representation of the current status. The output
503      * is only meant for debugging and is not guaranteed to be stable across
504      * releases and/or devices.
505      *
506      * @hide
507      */
getDexFileStatus(String fileName, String instructionSet)508     public static native String getDexFileStatus(String fileName, String instructionSet)
509         throws FileNotFoundException;
510 }
511