dalvik_system_DexFile.cpp revision c1a4ab9c313d8a3d12007f2dbef7b5a6fa4ac2ef
1/*
2 * Copyright (C) 2008 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/*
18 * dalvik.system.DexFile
19 */
20#include "Dalvik.h"
21#include "native/InternalNativePriv.h"
22
23/*
24 * Return true if the given name ends with ".dex".
25 */
26static bool hasDexExtension(const char* name) {
27    size_t len = strlen(name);
28
29    return (len >= 5)
30        && (name[len - 5] != '/')
31        && (strcmp(&name[len - 4], ".dex") == 0);
32}
33
34/*
35 * Internal struct for managing DexFile.
36 */
37struct DexOrJar {
38    char*       fileName;
39    bool        isDex;
40    bool        okayToFree;
41    RawDexFile* pRawDexFile;
42    JarFile*    pJarFile;
43    u1*         pDexMemory; // malloc()ed memory, if any
44};
45
46/*
47 * (This is a dvmHashTableFree callback.)
48 */
49void dvmFreeDexOrJar(void* vptr)
50{
51    DexOrJar* pDexOrJar = (DexOrJar*) vptr;
52
53    ALOGV("Freeing DexOrJar '%s'", pDexOrJar->fileName);
54
55    if (pDexOrJar->isDex)
56        dvmRawDexFileFree(pDexOrJar->pRawDexFile);
57    else
58        dvmJarFileFree(pDexOrJar->pJarFile);
59    free(pDexOrJar->fileName);
60    free(pDexOrJar->pDexMemory);
61    free(pDexOrJar);
62}
63
64/*
65 * (This is a dvmHashTableLookup compare func.)
66 *
67 * Args are DexOrJar*.
68 */
69static int hashcmpDexOrJar(const void* tableVal, const void* newVal)
70{
71    return (int) newVal - (int) tableVal;
72}
73
74/*
75 * Verify that the "cookie" is a DEX file we opened.
76 *
77 * Expects that the hash table will be *unlocked* here.
78 *
79 * If the cookie is invalid, we throw an exception and return "false".
80 */
81static bool validateCookie(int cookie)
82{
83    DexOrJar* pDexOrJar = (DexOrJar*) cookie;
84
85    LOGVV("+++ dex verifying cookie %p", pDexOrJar);
86
87    if (pDexOrJar == NULL)
88        return false;
89
90    u4 hash = cookie;
91    dvmHashTableLock(gDvm.userDexFiles);
92    void* result = dvmHashTableLookup(gDvm.userDexFiles, hash, pDexOrJar,
93                hashcmpDexOrJar, false);
94    dvmHashTableUnlock(gDvm.userDexFiles);
95    if (result == NULL) {
96        dvmThrowRuntimeException("invalid DexFile cookie");
97        return false;
98    }
99
100    return true;
101}
102
103
104/*
105 * Add given DexOrJar to the hash table of user-loaded dex files.
106 */
107static void addToDexFileTable(DexOrJar* pDexOrJar) {
108    /*
109     * Later on, we will receive this pointer as an argument and need
110     * to find it in the hash table without knowing if it's valid or
111     * not, which means we can't compute a hash value from anything
112     * inside DexOrJar. We don't share DexOrJar structs when the same
113     * file is opened multiple times, so we can just use the low 32
114     * bits of the pointer as the hash.
115     */
116    u4 hash = (u4) pDexOrJar;
117    void* result;
118
119    dvmHashTableLock(gDvm.userDexFiles);
120    result = dvmHashTableLookup(gDvm.userDexFiles, hash, pDexOrJar,
121            hashcmpDexOrJar, true);
122    dvmHashTableUnlock(gDvm.userDexFiles);
123
124    if (result != pDexOrJar) {
125        ALOGE("Pointer has already been added?");
126        dvmAbort();
127    }
128
129    pDexOrJar->okayToFree = true;
130}
131
132/*
133 * private static int openDexFile(String sourceName, String outputName,
134 *     int flags) throws IOException
135 *
136 * Open a DEX file, returning a pointer to our internal data structure.
137 *
138 * "sourceName" should point to the "source" jar or DEX file.
139 *
140 * If "outputName" is NULL, the DEX code will automatically find the
141 * "optimized" version in the cache directory, creating it if necessary.
142 * If it's non-NULL, the specified file will be used instead.
143 *
144 * TODO: at present we will happily open the same file more than once.
145 * To optimize this away we could search for existing entries in the hash
146 * table and refCount them.  Requires atomic ops or adding "synchronized"
147 * to the non-native code that calls here.
148 *
149 * TODO: should be using "long" for a pointer.
150 */
151static void Dalvik_dalvik_system_DexFile_openDexFile(const u4* args,
152    JValue* pResult)
153{
154    StringObject* sourceNameObj = (StringObject*) args[0];
155    StringObject* outputNameObj = (StringObject*) args[1];
156    DexOrJar* pDexOrJar = NULL;
157    JarFile* pJarFile;
158    RawDexFile* pRawDexFile;
159    char* sourceName;
160    char* outputName;
161
162    if (sourceNameObj == NULL) {
163        dvmThrowNullPointerException("sourceName == null");
164        RETURN_VOID();
165    }
166
167    sourceName = dvmCreateCstrFromString(sourceNameObj);
168    if (outputNameObj != NULL)
169        outputName = dvmCreateCstrFromString(outputNameObj);
170    else
171        outputName = NULL;
172
173    /*
174     * We have to deal with the possibility that somebody might try to
175     * open one of our bootstrap class DEX files.  The set of dependencies
176     * will be different, and hence the results of optimization might be
177     * different, which means we'd actually need to have two versions of
178     * the optimized DEX: one that only knows about part of the boot class
179     * path, and one that knows about everything in it.  The latter might
180     * optimize field/method accesses based on a class that appeared later
181     * in the class path.
182     *
183     * We can't let the user-defined class loader open it and start using
184     * the classes, since the optimized form of the code skips some of
185     * the method and field resolution that we would ordinarily do, and
186     * we'd have the wrong semantics.
187     *
188     * We have to reject attempts to manually open a DEX file from the boot
189     * class path.  The easiest way to do this is by filename, which works
190     * out because variations in name (e.g. "/system/framework/./ext.jar")
191     * result in us hitting a different dalvik-cache entry.  It's also fine
192     * if the caller specifies their own output file.
193     */
194    if (dvmClassPathContains(gDvm.bootClassPath, sourceName)) {
195        ALOGW("Refusing to reopen boot DEX '%s'", sourceName);
196        dvmThrowIOException(
197            "Re-opening BOOTCLASSPATH DEX files is not allowed");
198        free(sourceName);
199        free(outputName);
200        RETURN_VOID();
201    }
202
203    /*
204     * Try to open it directly as a DEX if the name ends with ".dex".
205     * If that fails (or isn't tried in the first place), try it as a
206     * Zip with a "classes.dex" inside.
207     */
208    if (hasDexExtension(sourceName)
209            && dvmRawDexFileOpen(sourceName, outputName, &pRawDexFile, false) == 0) {
210        ALOGV("Opening DEX file '%s' (DEX)", sourceName);
211
212        pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));
213        pDexOrJar->isDex = true;
214        pDexOrJar->pRawDexFile = pRawDexFile;
215        pDexOrJar->pDexMemory = NULL;
216    } else if (dvmJarFileOpen(sourceName, outputName, &pJarFile, false) == 0) {
217        ALOGV("Opening DEX file '%s' (Jar)", sourceName);
218
219        pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));
220        pDexOrJar->isDex = false;
221        pDexOrJar->pJarFile = pJarFile;
222        pDexOrJar->pDexMemory = NULL;
223    } else {
224        ALOGV("Unable to open DEX file '%s'", sourceName);
225        dvmThrowIOException("unable to open DEX file");
226    }
227
228    if (pDexOrJar != NULL) {
229        pDexOrJar->fileName = sourceName;
230        addToDexFileTable(pDexOrJar);
231    } else {
232        free(sourceName);
233    }
234
235    RETURN_PTR(pDexOrJar);
236}
237
238/*
239 * private static int openDexFile(byte[] fileContents) throws IOException
240 *
241 * Open a DEX file represented in a byte[], returning a pointer to our
242 * internal data structure.
243 *
244 * The system will only perform "essential" optimizations on the given file.
245 *
246 * TODO: should be using "long" for a pointer.
247 */
248static void Dalvik_dalvik_system_DexFile_openDexFile_bytearray(const u4* args,
249    JValue* pResult)
250{
251    ArrayObject* fileContentsObj = (ArrayObject*) args[0];
252    u4 length;
253    u1* pBytes;
254    RawDexFile* pRawDexFile;
255    DexOrJar* pDexOrJar = NULL;
256
257    if (fileContentsObj == NULL) {
258        dvmThrowNullPointerException("fileContents == null");
259        RETURN_VOID();
260    }
261
262    /* TODO: Avoid making a copy of the array. (note array *is* modified) */
263    length = fileContentsObj->length;
264    pBytes = (u1*) malloc(length);
265
266    if (pBytes == NULL) {
267        dvmThrowRuntimeException("unable to allocate DEX memory");
268        RETURN_VOID();
269    }
270
271    memcpy(pBytes, fileContentsObj->contents, length);
272
273    if (dvmRawDexFileOpenArray(pBytes, length, &pRawDexFile) != 0) {
274        ALOGV("Unable to open in-memory DEX file");
275        free(pBytes);
276        dvmThrowRuntimeException("unable to open in-memory DEX file");
277        RETURN_VOID();
278    }
279
280    ALOGV("Opening in-memory DEX");
281    pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));
282    pDexOrJar->isDex = true;
283    pDexOrJar->pRawDexFile = pRawDexFile;
284    pDexOrJar->pDexMemory = pBytes;
285    pDexOrJar->fileName = strdup("<memory>"); // Needs to be free()able.
286    addToDexFileTable(pDexOrJar);
287
288    RETURN_PTR(pDexOrJar);
289}
290
291/*
292 * private static void closeDexFile(int cookie)
293 *
294 * Release resources associated with a user-loaded DEX file.
295 */
296static void Dalvik_dalvik_system_DexFile_closeDexFile(const u4* args,
297    JValue* pResult)
298{
299    int cookie = args[0];
300    DexOrJar* pDexOrJar = (DexOrJar*) cookie;
301
302    if (pDexOrJar == NULL)
303        RETURN_VOID();
304    if (!validateCookie(cookie))
305        RETURN_VOID();
306
307    ALOGV("Closing DEX file %p (%s)", pDexOrJar, pDexOrJar->fileName);
308
309    /*
310     * We can't just free arbitrary DEX files because they have bits and
311     * pieces of loaded classes.  The only exception to this rule is if
312     * they were never used to load classes.
313     *
314     * If we can't free them here, dvmInternalNativeShutdown() will free
315     * them when the VM shuts down.
316     */
317    if (pDexOrJar->okayToFree) {
318        u4 hash = (u4) pDexOrJar;
319        dvmHashTableLock(gDvm.userDexFiles);
320        if (!dvmHashTableRemove(gDvm.userDexFiles, hash, pDexOrJar)) {
321            ALOGW("WARNING: could not remove '%s' from DEX hash table",
322                pDexOrJar->fileName);
323        }
324        dvmHashTableUnlock(gDvm.userDexFiles);
325        ALOGV("+++ freeing DexFile '%s' resources", pDexOrJar->fileName);
326        dvmFreeDexOrJar(pDexOrJar);
327    } else {
328        ALOGV("+++ NOT freeing DexFile '%s' resources", pDexOrJar->fileName);
329    }
330
331    RETURN_VOID();
332}
333
334/*
335 * private static Class defineClass(String name, ClassLoader loader,
336 *      int cookie)
337 *
338 * Load a class from a DEX file.  This is roughly equivalent to defineClass()
339 * in a regular VM -- it's invoked by the class loader to cause the
340 * creation of a specific class.  The difference is that the search for and
341 * reading of the bytes is done within the VM.
342 *
343 * The class name is a "binary name", e.g. "java.lang.String".
344 *
345 * Returns a null pointer with no exception if the class was not found.
346 * Throws an exception on other failures.
347 */
348static void Dalvik_dalvik_system_DexFile_defineClass(const u4* args,
349    JValue* pResult)
350{
351    StringObject* nameObj = (StringObject*) args[0];
352    Object* loader = (Object*) args[1];
353    int cookie = args[2];
354    ClassObject* clazz = NULL;
355    DexOrJar* pDexOrJar = (DexOrJar*) cookie;
356    DvmDex* pDvmDex;
357    char* name;
358    char* descriptor;
359
360    name = dvmCreateCstrFromString(nameObj);
361    descriptor = dvmDotToDescriptor(name);
362    ALOGV("--- Explicit class load '%s' l=%p c=0x%08x",
363        descriptor, loader, cookie);
364    free(name);
365
366    if (!validateCookie(cookie))
367        RETURN_VOID();
368
369    if (pDexOrJar->isDex)
370        pDvmDex = dvmGetRawDexFileDex(pDexOrJar->pRawDexFile);
371    else
372        pDvmDex = dvmGetJarFileDex(pDexOrJar->pJarFile);
373
374    /* once we load something, we can't unmap the storage */
375    pDexOrJar->okayToFree = false;
376
377    clazz = dvmDefineClass(pDvmDex, descriptor, loader);
378    Thread* self = dvmThreadSelf();
379    if (dvmCheckException(self)) {
380        /*
381         * If we threw a "class not found" exception, stifle it, since the
382         * contract in the higher method says we simply return null if
383         * the class is not found.
384         */
385        Object* excep = dvmGetException(self);
386        if (strcmp(excep->clazz->descriptor,
387                   "Ljava/lang/ClassNotFoundException;") == 0 ||
388            strcmp(excep->clazz->descriptor,
389                   "Ljava/lang/NoClassDefFoundError;") == 0)
390        {
391            dvmClearException(self);
392        }
393        clazz = NULL;
394    }
395
396    free(descriptor);
397    RETURN_PTR(clazz);
398}
399
400/*
401 * private static String[] getClassNameList(int cookie)
402 *
403 * Returns a String array that holds the names of all classes in the
404 * specified DEX file.
405 */
406static void Dalvik_dalvik_system_DexFile_getClassNameList(const u4* args,
407    JValue* pResult)
408{
409    int cookie = args[0];
410    DexOrJar* pDexOrJar = (DexOrJar*) cookie;
411    Thread* self = dvmThreadSelf();
412
413    if (!validateCookie(cookie))
414        RETURN_VOID();
415
416    DvmDex* pDvmDex;
417    if (pDexOrJar->isDex)
418        pDvmDex = dvmGetRawDexFileDex(pDexOrJar->pRawDexFile);
419    else
420        pDvmDex = dvmGetJarFileDex(pDexOrJar->pJarFile);
421    assert(pDvmDex != NULL);
422    DexFile* pDexFile = pDvmDex->pDexFile;
423
424    int count = pDexFile->pHeader->classDefsSize;
425    ClassObject* arrayClass =
426        dvmFindArrayClassForElement(gDvm.classJavaLangString);
427    ArrayObject* stringArray =
428        dvmAllocArrayByClass(arrayClass, count, ALLOC_DEFAULT);
429    if (stringArray == NULL) {
430        /* probably OOM */
431        ALOGD("Failed allocating array of %d strings", count);
432        assert(dvmCheckException(self));
433        RETURN_VOID();
434    }
435
436    int i;
437    for (i = 0; i < count; i++) {
438        const DexClassDef* pClassDef = dexGetClassDef(pDexFile, i);
439        const char* descriptor =
440            dexStringByTypeIdx(pDexFile, pClassDef->classIdx);
441
442        char* className = dvmDescriptorToDot(descriptor);
443        StringObject* str = dvmCreateStringFromCstr(className);
444        dvmSetObjectArrayElement(stringArray, i, (Object *)str);
445        dvmReleaseTrackedAlloc((Object *)str, self);
446        free(className);
447    }
448
449    dvmReleaseTrackedAlloc((Object*)stringArray, self);
450    RETURN_PTR(stringArray);
451}
452
453/*
454 * public static boolean isDexOptNeeded(String fileName)
455 *         throws FileNotFoundException, IOException
456 *
457 * Returns true if the VM believes that the apk/jar file is out of date
458 * and should be passed through "dexopt" again.
459 *
460 * @param fileName the absolute path to the apk/jar file to examine.
461 * @return true if dexopt should be called on the file, false otherwise.
462 * @throws java.io.FileNotFoundException if fileName is not readable,
463 *         not a file, or not present.
464 * @throws java.io.IOException if fileName is not a valid apk/jar file or
465 *         if problems occur while parsing it.
466 * @throws java.lang.NullPointerException if fileName is null.
467 * @throws dalvik.system.StaleDexCacheError if the optimized dex file
468 *         is stale but exists on a read-only partition.
469 */
470static void Dalvik_dalvik_system_DexFile_isDexOptNeeded(const u4* args,
471    JValue* pResult)
472{
473    StringObject* nameObj = (StringObject*) args[0];
474    char* name;
475    DexCacheStatus status;
476    int result;
477
478    name = dvmCreateCstrFromString(nameObj);
479    if (name == NULL) {
480        dvmThrowNullPointerException("fileName == null");
481        RETURN_VOID();
482    }
483    if (access(name, R_OK) != 0) {
484        dvmThrowFileNotFoundException(name);
485        free(name);
486        RETURN_VOID();
487    }
488    status = dvmDexCacheStatus(name);
489    ALOGV("dvmDexCacheStatus(%s) returned %d", name, status);
490
491    result = true;
492    switch (status) {
493    default: //FALLTHROUGH
494    case DEX_CACHE_BAD_ARCHIVE:
495        dvmThrowIOException(name);
496        result = -1;
497        break;
498    case DEX_CACHE_OK:
499        result = false;
500        break;
501    case DEX_CACHE_STALE:
502        result = true;
503        break;
504    case DEX_CACHE_STALE_ODEX:
505        dvmThrowStaleDexCacheError(name);
506        result = -1;
507        break;
508    }
509    free(name);
510
511    if (result >= 0) {
512        RETURN_BOOLEAN(result);
513    } else {
514        RETURN_VOID();
515    }
516}
517
518const DalvikNativeMethod dvm_dalvik_system_DexFile[] = {
519    { "openDexFile",        "(Ljava/lang/String;Ljava/lang/String;I)I",
520        Dalvik_dalvik_system_DexFile_openDexFile },
521    { "openDexFile",        "([B)I",
522        Dalvik_dalvik_system_DexFile_openDexFile_bytearray },
523    { "closeDexFile",       "(I)V",
524        Dalvik_dalvik_system_DexFile_closeDexFile },
525    { "defineClass",        "(Ljava/lang/String;Ljava/lang/ClassLoader;I)Ljava/lang/Class;",
526        Dalvik_dalvik_system_DexFile_defineClass },
527    { "getClassNameList",   "(I)[Ljava/lang/String;",
528        Dalvik_dalvik_system_DexFile_getClassNameList },
529    { "isDexOptNeeded",     "(Ljava/lang/String;)Z",
530        Dalvik_dalvik_system_DexFile_isDexOptNeeded },
531    { NULL, NULL, NULL },
532};
533