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 openDexFileNative(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_openDexFileNative(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    free(outputName);
236    RETURN_PTR(pDexOrJar);
237}
238
239/*
240 * private static int openDexFile(byte[] fileContents) throws IOException
241 *
242 * Open a DEX file represented in a byte[], returning a pointer to our
243 * internal data structure.
244 *
245 * The system will only perform "essential" optimizations on the given file.
246 *
247 * TODO: should be using "long" for a pointer.
248 */
249static void Dalvik_dalvik_system_DexFile_openDexFile_bytearray(const u4* args,
250    JValue* pResult)
251{
252    ArrayObject* fileContentsObj = (ArrayObject*) args[0];
253    u4 length;
254    u1* pBytes;
255    RawDexFile* pRawDexFile;
256    DexOrJar* pDexOrJar = NULL;
257
258    if (fileContentsObj == NULL) {
259        dvmThrowNullPointerException("fileContents == null");
260        RETURN_VOID();
261    }
262
263    /* TODO: Avoid making a copy of the array. (note array *is* modified) */
264    length = fileContentsObj->length;
265    pBytes = (u1*) malloc(length);
266
267    if (pBytes == NULL) {
268        dvmThrowRuntimeException("unable to allocate DEX memory");
269        RETURN_VOID();
270    }
271
272    memcpy(pBytes, fileContentsObj->contents, length);
273
274    if (dvmRawDexFileOpenArray(pBytes, length, &pRawDexFile) != 0) {
275        ALOGV("Unable to open in-memory DEX file");
276        free(pBytes);
277        dvmThrowRuntimeException("unable to open in-memory DEX file");
278        RETURN_VOID();
279    }
280
281    ALOGV("Opening in-memory DEX");
282    pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));
283    pDexOrJar->isDex = true;
284    pDexOrJar->pRawDexFile = pRawDexFile;
285    pDexOrJar->pDexMemory = pBytes;
286    pDexOrJar->fileName = strdup("<memory>"); // Needs to be free()able.
287    addToDexFileTable(pDexOrJar);
288
289    RETURN_PTR(pDexOrJar);
290}
291
292/*
293 * private static void closeDexFile(int cookie)
294 *
295 * Release resources associated with a user-loaded DEX file.
296 */
297static void Dalvik_dalvik_system_DexFile_closeDexFile(const u4* args,
298    JValue* pResult)
299{
300    int cookie = args[0];
301    DexOrJar* pDexOrJar = (DexOrJar*) cookie;
302
303    if (pDexOrJar == NULL)
304        RETURN_VOID();
305    if (!validateCookie(cookie))
306        RETURN_VOID();
307
308    ALOGV("Closing DEX file %p (%s)", pDexOrJar, pDexOrJar->fileName);
309
310    /*
311     * We can't just free arbitrary DEX files because they have bits and
312     * pieces of loaded classes.  The only exception to this rule is if
313     * they were never used to load classes.
314     *
315     * If we can't free them here, dvmInternalNativeShutdown() will free
316     * them when the VM shuts down.
317     */
318    if (pDexOrJar->okayToFree) {
319        u4 hash = (u4) pDexOrJar;
320        dvmHashTableLock(gDvm.userDexFiles);
321        if (!dvmHashTableRemove(gDvm.userDexFiles, hash, pDexOrJar)) {
322            ALOGW("WARNING: could not remove '%s' from DEX hash table",
323                pDexOrJar->fileName);
324        }
325        dvmHashTableUnlock(gDvm.userDexFiles);
326        ALOGV("+++ freeing DexFile '%s' resources", pDexOrJar->fileName);
327        dvmFreeDexOrJar(pDexOrJar);
328    } else {
329        ALOGV("+++ NOT freeing DexFile '%s' resources", pDexOrJar->fileName);
330    }
331
332    RETURN_VOID();
333}
334
335/*
336 * private static Class defineClassNative(String name, ClassLoader loader,
337 *      int cookie)
338 *
339 * Load a class from a DEX file.  This is roughly equivalent to defineClass()
340 * in a regular VM -- it's invoked by the class loader to cause the
341 * creation of a specific class.  The difference is that the search for and
342 * reading of the bytes is done within the VM.
343 *
344 * The class name is a "binary name", e.g. "java.lang.String".
345 *
346 * Returns a null pointer with no exception if the class was not found.
347 * Throws an exception on other failures.
348 */
349static void Dalvik_dalvik_system_DexFile_defineClassNative(const u4* args,
350    JValue* pResult)
351{
352    StringObject* nameObj = (StringObject*) args[0];
353    Object* loader = (Object*) args[1];
354    int cookie = args[2];
355    ClassObject* clazz = NULL;
356    DexOrJar* pDexOrJar = (DexOrJar*) cookie;
357    DvmDex* pDvmDex;
358    char* name;
359    char* descriptor;
360
361    name = dvmCreateCstrFromString(nameObj);
362    descriptor = dvmDotToDescriptor(name);
363    ALOGV("--- Explicit class load '%s' l=%p c=0x%08x",
364        descriptor, loader, cookie);
365    free(name);
366
367    if (!validateCookie(cookie))
368        RETURN_VOID();
369
370    if (pDexOrJar->isDex)
371        pDvmDex = dvmGetRawDexFileDex(pDexOrJar->pRawDexFile);
372    else
373        pDvmDex = dvmGetJarFileDex(pDexOrJar->pJarFile);
374
375    /* once we load something, we can't unmap the storage */
376    pDexOrJar->okayToFree = false;
377
378    clazz = dvmDefineClass(pDvmDex, descriptor, loader);
379    Thread* self = dvmThreadSelf();
380    if (dvmCheckException(self)) {
381        /*
382         * If we threw a "class not found" exception, stifle it, since the
383         * contract in the higher method says we simply return null if
384         * the class is not found.
385         */
386        Object* excep = dvmGetException(self);
387        if (strcmp(excep->clazz->descriptor,
388                   "Ljava/lang/ClassNotFoundException;") == 0 ||
389            strcmp(excep->clazz->descriptor,
390                   "Ljava/lang/NoClassDefFoundError;") == 0)
391        {
392            dvmClearException(self);
393        }
394        clazz = NULL;
395    }
396
397    free(descriptor);
398    RETURN_PTR(clazz);
399}
400
401/*
402 * private static String[] getClassNameList(int cookie)
403 *
404 * Returns a String array that holds the names of all classes in the
405 * specified DEX file.
406 */
407static void Dalvik_dalvik_system_DexFile_getClassNameList(const u4* args,
408    JValue* pResult)
409{
410    int cookie = args[0];
411    DexOrJar* pDexOrJar = (DexOrJar*) cookie;
412    Thread* self = dvmThreadSelf();
413
414    if (!validateCookie(cookie))
415        RETURN_VOID();
416
417    DvmDex* pDvmDex;
418    if (pDexOrJar->isDex)
419        pDvmDex = dvmGetRawDexFileDex(pDexOrJar->pRawDexFile);
420    else
421        pDvmDex = dvmGetJarFileDex(pDexOrJar->pJarFile);
422    assert(pDvmDex != NULL);
423    DexFile* pDexFile = pDvmDex->pDexFile;
424
425    int count = pDexFile->pHeader->classDefsSize;
426    ClassObject* arrayClass =
427        dvmFindArrayClassForElement(gDvm.classJavaLangString);
428    ArrayObject* stringArray =
429        dvmAllocArrayByClass(arrayClass, count, ALLOC_DEFAULT);
430    if (stringArray == NULL) {
431        /* probably OOM */
432        ALOGD("Failed allocating array of %d strings", count);
433        assert(dvmCheckException(self));
434        RETURN_VOID();
435    }
436
437    int i;
438    for (i = 0; i < count; i++) {
439        const DexClassDef* pClassDef = dexGetClassDef(pDexFile, i);
440        const char* descriptor =
441            dexStringByTypeIdx(pDexFile, pClassDef->classIdx);
442
443        char* className = dvmDescriptorToDot(descriptor);
444        StringObject* str = dvmCreateStringFromCstr(className);
445        dvmSetObjectArrayElement(stringArray, i, (Object *)str);
446        dvmReleaseTrackedAlloc((Object *)str, self);
447        free(className);
448    }
449
450    dvmReleaseTrackedAlloc((Object*)stringArray, self);
451    RETURN_PTR(stringArray);
452}
453
454/*
455 * public static boolean isDexOptNeeded(String fileName)
456 *         throws FileNotFoundException, IOException
457 *
458 * Returns true if the VM believes that the apk/jar file is out of date
459 * and should be passed through "dexopt" again.
460 *
461 * @param fileName the absolute path to the apk/jar file to examine.
462 * @return true if dexopt should be called on the file, false otherwise.
463 * @throws java.io.FileNotFoundException if fileName is not readable,
464 *         not a file, or not present.
465 * @throws java.io.IOException if fileName is not a valid apk/jar file or
466 *         if problems occur while parsing it.
467 * @throws java.lang.NullPointerException if fileName is null.
468 * @throws dalvik.system.StaleDexCacheError if the optimized dex file
469 *         is stale but exists on a read-only partition.
470 */
471static void Dalvik_dalvik_system_DexFile_isDexOptNeeded(const u4* args,
472    JValue* pResult)
473{
474    StringObject* nameObj = (StringObject*) args[0];
475    char* name;
476    DexCacheStatus status;
477    int result;
478
479    name = dvmCreateCstrFromString(nameObj);
480    if (name == NULL) {
481        dvmThrowNullPointerException("fileName == null");
482        RETURN_VOID();
483    }
484    if (access(name, R_OK) != 0) {
485        dvmThrowFileNotFoundException(name);
486        free(name);
487        RETURN_VOID();
488    }
489    status = dvmDexCacheStatus(name);
490    ALOGV("dvmDexCacheStatus(%s) returned %d", name, status);
491
492    result = true;
493    switch (status) {
494    default: //FALLTHROUGH
495    case DEX_CACHE_BAD_ARCHIVE:
496        dvmThrowIOException(name);
497        result = -1;
498        break;
499    case DEX_CACHE_OK:
500        result = false;
501        break;
502    case DEX_CACHE_STALE:
503        result = true;
504        break;
505    case DEX_CACHE_STALE_ODEX:
506        dvmThrowStaleDexCacheError(name);
507        result = -1;
508        break;
509    }
510    free(name);
511
512    if (result >= 0) {
513        RETURN_BOOLEAN(result);
514    } else {
515        RETURN_VOID();
516    }
517}
518
519const DalvikNativeMethod dvm_dalvik_system_DexFile[] = {
520    { "openDexFileNative",  "(Ljava/lang/String;Ljava/lang/String;I)I",
521        Dalvik_dalvik_system_DexFile_openDexFileNative },
522    { "openDexFile",        "([B)I",
523        Dalvik_dalvik_system_DexFile_openDexFile_bytearray },
524    { "closeDexFile",       "(I)V",
525        Dalvik_dalvik_system_DexFile_closeDexFile },
526    { "defineClassNative",  "(Ljava/lang/String;Ljava/lang/ClassLoader;I)Ljava/lang/Class;",
527        Dalvik_dalvik_system_DexFile_defineClassNative },
528    { "getClassNameList",   "(I)[Ljava/lang/String;",
529        Dalvik_dalvik_system_DexFile_getClassNameList },
530    { "isDexOptNeeded",     "(Ljava/lang/String;)Z",
531        Dalvik_dalvik_system_DexFile_isDexOptNeeded },
532    { NULL, NULL, NULL },
533};
534