/*
* 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 },
};