summaryrefslogtreecommitdiff
path: root/vm/native/dalvik_system_DexFile.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'vm/native/dalvik_system_DexFile.cpp')
-rw-r--r--vm/native/dalvik_system_DexFile.cpp532
1 files changed, 532 insertions, 0 deletions
diff --git a/vm/native/dalvik_system_DexFile.cpp b/vm/native/dalvik_system_DexFile.cpp
new file mode 100644
index 0000000..f611410
--- /dev/null
+++ b/vm/native/dalvik_system_DexFile.cpp
@@ -0,0 +1,532 @@
+/*
+ * Copyright (C) 2008 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.
+ */
+
+/*
+ * dalvik.system.DexFile
+ */
+#include "Dalvik.h"
+#include "native/InternalNativePriv.h"
+
+/*
+ * Return true if the given name ends with ".dex".
+ */
+static bool hasDexExtension(const char* name) {
+ size_t len = strlen(name);
+
+ return (len >= 5)
+ && (name[len - 5] != '/')
+ && (strcmp(&name[len - 4], ".dex") == 0);
+}
+
+/*
+ * Internal struct for managing DexFile.
+ */
+struct DexOrJar {
+ char* fileName;
+ bool isDex;
+ bool okayToFree;
+ RawDexFile* pRawDexFile;
+ JarFile* pJarFile;
+ u1* pDexMemory; // malloc()ed memory, if any
+};
+
+/*
+ * (This is a dvmHashTableFree callback.)
+ */
+void dvmFreeDexOrJar(void* vptr)
+{
+ DexOrJar* pDexOrJar = (DexOrJar*) vptr;
+
+ LOGV("Freeing DexOrJar '%s'", pDexOrJar->fileName);
+
+ if (pDexOrJar->isDex)
+ dvmRawDexFileFree(pDexOrJar->pRawDexFile);
+ else
+ dvmJarFileFree(pDexOrJar->pJarFile);
+ free(pDexOrJar->fileName);
+ free(pDexOrJar->pDexMemory);
+ free(pDexOrJar);
+}
+
+/*
+ * (This is a dvmHashTableLookup compare func.)
+ *
+ * Args are DexOrJar*.
+ */
+static int hashcmpDexOrJar(const void* tableVal, const void* newVal)
+{
+ return (int) newVal - (int) tableVal;
+}
+
+/*
+ * Verify that the "cookie" is a DEX file we opened.
+ *
+ * Expects that the hash table will be *unlocked* here.
+ *
+ * If the cookie is invalid, we throw an exception and return "false".
+ */
+static bool validateCookie(int cookie)
+{
+ DexOrJar* pDexOrJar = (DexOrJar*) cookie;
+
+ LOGVV("+++ dex verifying cookie %p", pDexOrJar);
+
+ if (pDexOrJar == NULL)
+ return false;
+
+ u4 hash = cookie;
+ dvmHashTableLock(gDvm.userDexFiles);
+ void* result = dvmHashTableLookup(gDvm.userDexFiles, hash, pDexOrJar,
+ hashcmpDexOrJar, false);
+ dvmHashTableUnlock(gDvm.userDexFiles);
+ if (result == NULL) {
+ dvmThrowRuntimeException("invalid DexFile cookie");
+ return false;
+ }
+
+ return true;
+}
+
+
+/*
+ * Add given DexOrJar to the hash table of user-loaded dex files.
+ */
+static void addToDexFileTable(DexOrJar* pDexOrJar) {
+ /*
+ * Later on, we will receive this pointer as an argument and need
+ * to find it in the hash table without knowing if it's valid or
+ * not, which means we can't compute a hash value from anything
+ * inside DexOrJar. We don't share DexOrJar structs when the same
+ * file is opened multiple times, so we can just use the low 32
+ * bits of the pointer as the hash.
+ */
+ u4 hash = (u4) pDexOrJar;
+ void* result;
+
+ dvmHashTableLock(gDvm.userDexFiles);
+ result = dvmHashTableLookup(gDvm.userDexFiles, hash, pDexOrJar,
+ hashcmpDexOrJar, true);
+ dvmHashTableUnlock(gDvm.userDexFiles);
+
+ if (result != pDexOrJar) {
+ LOGE("Pointer has already been added?");
+ dvmAbort();
+ }
+
+ pDexOrJar->okayToFree = true;
+}
+
+/*
+ * private static int openDexFile(String sourceName, String outputName,
+ * int flags) throws IOException
+ *
+ * Open a DEX file, returning a pointer to our internal data structure.
+ *
+ * "sourceName" should point to the "source" jar or DEX file.
+ *
+ * If "outputName" is NULL, the DEX code will automatically find the
+ * "optimized" version in the cache directory, creating it if necessary.
+ * If it's non-NULL, the specified file will be used instead.
+ *
+ * TODO: at present we will happily open the same file more than once.
+ * To optimize this away we could search for existing entries in the hash
+ * table and refCount them. Requires atomic ops or adding "synchronized"
+ * to the non-native code that calls here.
+ *
+ * TODO: should be using "long" for a pointer.
+ */
+static void Dalvik_dalvik_system_DexFile_openDexFile(const u4* args,
+ JValue* pResult)
+{
+ StringObject* sourceNameObj = (StringObject*) args[0];
+ StringObject* outputNameObj = (StringObject*) args[1];
+ DexOrJar* pDexOrJar = NULL;
+ JarFile* pJarFile;
+ RawDexFile* pRawDexFile;
+ char* sourceName;
+ char* outputName;
+
+ if (sourceNameObj == NULL) {
+ dvmThrowNullPointerException("sourceName == null");
+ RETURN_VOID();
+ }
+
+ sourceName = dvmCreateCstrFromString(sourceNameObj);
+ if (outputNameObj != NULL)
+ outputName = dvmCreateCstrFromString(outputNameObj);
+ else
+ outputName = NULL;
+
+ /*
+ * We have to deal with the possibility that somebody might try to
+ * open one of our bootstrap class DEX files. The set of dependencies
+ * will be different, and hence the results of optimization might be
+ * different, which means we'd actually need to have two versions of
+ * the optimized DEX: one that only knows about part of the boot class
+ * path, and one that knows about everything in it. The latter might
+ * optimize field/method accesses based on a class that appeared later
+ * in the class path.
+ *
+ * We can't let the user-defined class loader open it and start using
+ * the classes, since the optimized form of the code skips some of
+ * the method and field resolution that we would ordinarily do, and
+ * we'd have the wrong semantics.
+ *
+ * We have to reject attempts to manually open a DEX file from the boot
+ * class path. The easiest way to do this is by filename, which works
+ * out because variations in name (e.g. "/system/framework/./ext.jar")
+ * result in us hitting a different dalvik-cache entry. It's also fine
+ * if the caller specifies their own output file.
+ */
+ if (dvmClassPathContains(gDvm.bootClassPath, sourceName)) {
+ LOGW("Refusing to reopen boot DEX '%s'", sourceName);
+ dvmThrowIOException(
+ "Re-opening BOOTCLASSPATH DEX files is not allowed");
+ free(sourceName);
+ free(outputName);
+ RETURN_VOID();
+ }
+
+ /*
+ * Try to open it directly as a DEX if the name ends with ".dex".
+ * If that fails (or isn't tried in the first place), try it as a
+ * Zip with a "classes.dex" inside.
+ */
+ if (hasDexExtension(sourceName)
+ && dvmRawDexFileOpen(sourceName, outputName, &pRawDexFile, false) == 0) {
+ LOGV("Opening DEX file '%s' (DEX)", sourceName);
+
+ pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));
+ pDexOrJar->isDex = true;
+ pDexOrJar->pRawDexFile = pRawDexFile;
+ pDexOrJar->pDexMemory = NULL;
+ } else if (dvmJarFileOpen(sourceName, outputName, &pJarFile, false) == 0) {
+ LOGV("Opening DEX file '%s' (Jar)", sourceName);
+
+ pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));
+ pDexOrJar->isDex = false;
+ pDexOrJar->pJarFile = pJarFile;
+ pDexOrJar->pDexMemory = NULL;
+ } else {
+ LOGV("Unable to open DEX file '%s'", sourceName);
+ dvmThrowIOException("unable to open DEX file");
+ }
+
+ if (pDexOrJar != NULL) {
+ pDexOrJar->fileName = sourceName;
+ addToDexFileTable(pDexOrJar);
+ } else {
+ free(sourceName);
+ }
+
+ RETURN_PTR(pDexOrJar);
+}
+
+/*
+ * private static int openDexFile(byte[] fileContents) throws IOException
+ *
+ * Open a DEX file represented in a byte[], returning a pointer to our
+ * internal data structure.
+ *
+ * The system will only perform "essential" optimizations on the given file.
+ *
+ * TODO: should be using "long" for a pointer.
+ */
+static void Dalvik_dalvik_system_DexFile_openDexFile_bytearray(const u4* args,
+ JValue* pResult)
+{
+ ArrayObject* fileContentsObj = (ArrayObject*) args[0];
+ u4 length;
+ u1* pBytes;
+ RawDexFile* pRawDexFile;
+ DexOrJar* pDexOrJar = NULL;
+
+ if (fileContentsObj == NULL) {
+ dvmThrowNullPointerException("fileContents == null");
+ RETURN_VOID();
+ }
+
+ /* TODO: Avoid making a copy of the array. (note array *is* modified) */
+ length = fileContentsObj->length;
+ pBytes = (u1*) malloc(length);
+
+ if (pBytes == NULL) {
+ dvmThrowRuntimeException("unable to allocate DEX memory");
+ RETURN_VOID();
+ }
+
+ memcpy(pBytes, fileContentsObj->contents, length);
+
+ if (dvmRawDexFileOpenArray(pBytes, length, &pRawDexFile) != 0) {
+ LOGV("Unable to open in-memory DEX file");
+ free(pBytes);
+ dvmThrowRuntimeException("unable to open in-memory DEX file");
+ RETURN_VOID();
+ }
+
+ LOGV("Opening in-memory DEX");
+ pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));
+ pDexOrJar->isDex = true;
+ pDexOrJar->pRawDexFile = pRawDexFile;
+ pDexOrJar->pDexMemory = pBytes;
+ pDexOrJar->fileName = strdup("<memory>"); // Needs to be free()able.
+ addToDexFileTable(pDexOrJar);
+
+ RETURN_PTR(pDexOrJar);
+}
+
+/*
+ * private static void closeDexFile(int cookie)
+ *
+ * Release resources associated with a user-loaded DEX file.
+ */
+static void Dalvik_dalvik_system_DexFile_closeDexFile(const u4* args,
+ JValue* pResult)
+{
+ int cookie = args[0];
+ DexOrJar* pDexOrJar = (DexOrJar*) cookie;
+
+ if (pDexOrJar == NULL)
+ RETURN_VOID();
+ if (!validateCookie(cookie))
+ RETURN_VOID();
+
+ LOGV("Closing DEX file %p (%s)", pDexOrJar, pDexOrJar->fileName);
+
+ /*
+ * We can't just free arbitrary DEX files because they have bits and
+ * pieces of loaded classes. The only exception to this rule is if
+ * they were never used to load classes.
+ *
+ * If we can't free them here, dvmInternalNativeShutdown() will free
+ * them when the VM shuts down.
+ */
+ if (pDexOrJar->okayToFree) {
+ u4 hash = (u4) pDexOrJar;
+ dvmHashTableLock(gDvm.userDexFiles);
+ if (!dvmHashTableRemove(gDvm.userDexFiles, hash, pDexOrJar)) {
+ LOGW("WARNING: could not remove '%s' from DEX hash table",
+ pDexOrJar->fileName);
+ }
+ dvmHashTableUnlock(gDvm.userDexFiles);
+ LOGV("+++ freeing DexFile '%s' resources", pDexOrJar->fileName);
+ dvmFreeDexOrJar(pDexOrJar);
+ } else {
+ LOGV("+++ NOT freeing DexFile '%s' resources", pDexOrJar->fileName);
+ }
+
+ RETURN_VOID();
+}
+
+/*
+ * private static Class defineClass(String name, ClassLoader loader,
+ * int cookie)
+ *
+ * Load a class from a DEX file. This is roughly equivalent to defineClass()
+ * in a regular VM -- it's invoked by the class loader to cause the
+ * creation of a specific class. The difference is that the search for and
+ * reading of the bytes is done within the VM.
+ *
+ * The class name is a "binary name", e.g. "java.lang.String".
+ *
+ * Returns a null pointer with no exception if the class was not found.
+ * Throws an exception on other failures.
+ */
+static void Dalvik_dalvik_system_DexFile_defineClass(const u4* args,
+ JValue* pResult)
+{
+ StringObject* nameObj = (StringObject*) args[0];
+ Object* loader = (Object*) args[1];
+ int cookie = args[2];
+ ClassObject* clazz = NULL;
+ DexOrJar* pDexOrJar = (DexOrJar*) cookie;
+ DvmDex* pDvmDex;
+ char* name;
+ char* descriptor;
+
+ name = dvmCreateCstrFromString(nameObj);
+ descriptor = dvmDotToDescriptor(name);
+ LOGV("--- Explicit class load '%s' l=%p c=0x%08x",
+ descriptor, loader, cookie);
+ free(name);
+
+ if (!validateCookie(cookie))
+ RETURN_VOID();
+
+ if (pDexOrJar->isDex)
+ pDvmDex = dvmGetRawDexFileDex(pDexOrJar->pRawDexFile);
+ else
+ pDvmDex = dvmGetJarFileDex(pDexOrJar->pJarFile);
+
+ /* once we load something, we can't unmap the storage */
+ pDexOrJar->okayToFree = false;
+
+ clazz = dvmDefineClass(pDvmDex, descriptor, loader);
+ Thread* self = dvmThreadSelf();
+ if (dvmCheckException(self)) {
+ /*
+ * If we threw a "class not found" exception, stifle it, since the
+ * contract in the higher method says we simply return null if
+ * the class is not found.
+ */
+ Object* excep = dvmGetException(self);
+ if (strcmp(excep->clazz->descriptor,
+ "Ljava/lang/ClassNotFoundException;") == 0 ||
+ strcmp(excep->clazz->descriptor,
+ "Ljava/lang/NoClassDefFoundError;") == 0)
+ {
+ dvmClearException(self);
+ }
+ clazz = NULL;
+ }
+
+ free(descriptor);
+ RETURN_PTR(clazz);
+}
+
+/*
+ * private static String[] getClassNameList(int cookie)
+ *
+ * Returns a String array that holds the names of all classes in the
+ * specified DEX file.
+ */
+static void Dalvik_dalvik_system_DexFile_getClassNameList(const u4* args,
+ JValue* pResult)
+{
+ int cookie = args[0];
+ DexOrJar* pDexOrJar = (DexOrJar*) cookie;
+ Thread* self = dvmThreadSelf();
+
+ if (!validateCookie(cookie))
+ RETURN_VOID();
+
+ DvmDex* pDvmDex;
+ if (pDexOrJar->isDex)
+ pDvmDex = dvmGetRawDexFileDex(pDexOrJar->pRawDexFile);
+ else
+ pDvmDex = dvmGetJarFileDex(pDexOrJar->pJarFile);
+ assert(pDvmDex != NULL);
+ DexFile* pDexFile = pDvmDex->pDexFile;
+
+ int count = pDexFile->pHeader->classDefsSize;
+ ClassObject* arrayClass =
+ dvmFindArrayClassForElement(gDvm.classJavaLangString);
+ ArrayObject* stringArray =
+ dvmAllocArrayByClass(arrayClass, count, ALLOC_DEFAULT);
+ if (stringArray == NULL) {
+ /* probably OOM */
+ LOGD("Failed allocating array of %d strings", count);
+ assert(dvmCheckException(self));
+ RETURN_VOID();
+ }
+
+ int i;
+ for (i = 0; i < count; i++) {
+ const DexClassDef* pClassDef = dexGetClassDef(pDexFile, i);
+ const char* descriptor =
+ dexStringByTypeIdx(pDexFile, pClassDef->classIdx);
+
+ char* className = dvmDescriptorToDot(descriptor);
+ StringObject* str = dvmCreateStringFromCstr(className);
+ dvmSetObjectArrayElement(stringArray, i, (Object *)str);
+ dvmReleaseTrackedAlloc((Object *)str, self);
+ free(className);
+ }
+
+ dvmReleaseTrackedAlloc((Object*)stringArray, self);
+ RETURN_PTR(stringArray);
+}
+
+/*
+ * public static boolean isDexOptNeeded(String fileName)
+ * throws FileNotFoundException, IOException
+ *
+ * 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.
+ * @throws dalvik.system.StaleDexCacheError if the optimized dex file
+ * is stale but exists on a read-only partition.
+ */
+static void Dalvik_dalvik_system_DexFile_isDexOptNeeded(const u4* args,
+ JValue* pResult)
+{
+ StringObject* nameObj = (StringObject*) args[0];
+ char* name;
+ DexCacheStatus status;
+ int result;
+
+ name = dvmCreateCstrFromString(nameObj);
+ if (name == NULL) {
+ dvmThrowNullPointerException("fileName == null");
+ RETURN_VOID();
+ }
+ if (access(name, R_OK) != 0) {
+ dvmThrowFileNotFoundException(name);
+ free(name);
+ RETURN_VOID();
+ }
+ status = dvmDexCacheStatus(name);
+ LOGV("dvmDexCacheStatus(%s) returned %d", name, status);
+
+ result = true;
+ switch (status) {
+ default: //FALLTHROUGH
+ case DEX_CACHE_BAD_ARCHIVE:
+ dvmThrowIOException(name);
+ result = -1;
+ break;
+ case DEX_CACHE_OK:
+ result = false;
+ break;
+ case DEX_CACHE_STALE:
+ result = true;
+ break;
+ case DEX_CACHE_STALE_ODEX:
+ dvmThrowStaleDexCacheError(name);
+ result = -1;
+ break;
+ }
+ free(name);
+
+ if (result >= 0) {
+ RETURN_BOOLEAN(result);
+ } else {
+ RETURN_VOID();
+ }
+}
+
+const DalvikNativeMethod dvm_dalvik_system_DexFile[] = {
+ { "openDexFile", "(Ljava/lang/String;Ljava/lang/String;I)I",
+ Dalvik_dalvik_system_DexFile_openDexFile },
+ { "openDexFile", "([B)I",
+ Dalvik_dalvik_system_DexFile_openDexFile_bytearray },
+ { "closeDexFile", "(I)V",
+ Dalvik_dalvik_system_DexFile_closeDexFile },
+ { "defineClass", "(Ljava/lang/String;Ljava/lang/ClassLoader;I)Ljava/lang/Class;",
+ Dalvik_dalvik_system_DexFile_defineClass },
+ { "getClassNameList", "(I)[Ljava/lang/String;",
+ Dalvik_dalvik_system_DexFile_getClassNameList },
+ { "isDexOptNeeded", "(Ljava/lang/String;)Z",
+ Dalvik_dalvik_system_DexFile_isDexOptNeeded },
+ { NULL, NULL, NULL },
+};