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