/*
 * 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.VMStack
 */
#include "Dalvik.h"
#include "UniquePtr.h"
#include "native/InternalNativePriv.h"

/*
 * public static ClassLoader getCallingClassLoader()
 *
 * Return the defining class loader of the caller's caller.
 */
static void Dalvik_dalvik_system_VMStack_getCallingClassLoader(const u4* args,
    JValue* pResult)
{
    ClassObject* clazz =
        dvmGetCaller2Class(dvmThreadSelf()->interpSave.curFrame);

    UNUSED_PARAMETER(args);

    if (clazz == NULL)
        RETURN_PTR(NULL);
    RETURN_PTR(clazz->classLoader);
}

/*
 * public static Class<?> getStackClass2()
 *
 * Returns the class of the caller's caller's caller.
 */
static void Dalvik_dalvik_system_VMStack_getStackClass2(const u4* args,
    JValue* pResult)
{
    ClassObject* clazz =
        dvmGetCaller3Class(dvmThreadSelf()->interpSave.curFrame);

    UNUSED_PARAMETER(args);

    RETURN_PTR(clazz);
}

/*
 * public static Class<?>[] getClasses(int maxDepth)
 *
 * Create an array of classes for the methods on the stack, skipping the
 * first two and all reflection methods.  If "stopAtPrivileged" is set,
 * stop shortly after we encounter a privileged class.
 */
static void Dalvik_dalvik_system_VMStack_getClasses(const u4* args,
    JValue* pResult)
{
    /* note "maxSize" is unsigned, so -1 turns into a very large value */
    size_t maxSize = args[0];
    size_t size = 0;
    const size_t kSkip = 2;

    /*
     * Get an array with the stack trace in it.
     */
    void *fp = dvmThreadSelf()->interpSave.curFrame;
    size_t depth = dvmComputeExactFrameDepth(fp);
    UniquePtr<const Method*[]> methods(new const Method*[depth]);
    dvmFillStackTraceArray(fp, methods.get(), depth);

    /*
     * Run through the array and count up how many elements there are.
     */
    for (size_t i = kSkip; i < depth && size < maxSize; ++i) {
        const Method* meth = methods[i];

        if (dvmIsReflectionMethod(meth))
            continue;

        size++;
    }

    /*
     * Create an array object to hold the classes.
     * TODO: can use gDvm.classJavaLangClassArray here?
     */
    ClassObject* classArrayClass = dvmFindArrayClass("[Ljava/lang/Class;",
                                                     NULL);
    if (classArrayClass == NULL) {
        ALOGW("Unable to find java.lang.Class array class");
        return;
    }
    ArrayObject* classes = dvmAllocArrayByClass(classArrayClass,
                                                size,
                                                ALLOC_DEFAULT);
    if (classes == NULL) {
        ALOGW("Unable to allocate class array of %zd elements", size);
        return;
    }

    /*
     * Fill in the array.
     */
    size_t objCount = 0;
    for (size_t i = kSkip; i < depth; ++i) {
        if (dvmIsReflectionMethod(methods[i])) {
            continue;
        }
        Object* klass = (Object *)methods[i]->clazz;
        dvmSetObjectArrayElement(classes, objCount, klass);
        objCount++;
    }
    assert(objCount == classes->length);

    dvmReleaseTrackedAlloc((Object*)classes, NULL);
    RETURN_PTR(classes);
}

/*
 * Return a trace buffer for the specified thread or NULL if the
 * thread is not still alive. *depth is set to the length of a
 * non-NULL trace buffer. Caller is responsible for freeing the trace
 * buffer.
 */
static int* getTraceBuf(Object* targetThreadObj, size_t* pStackDepth)
{
    Thread* self = dvmThreadSelf();
    Thread* thread;
    int* traceBuf;

    assert(targetThreadObj != NULL);

    dvmLockThreadList(self);

    /*
     * Make sure the thread is still alive and in the list.
     */
    for (thread = gDvm.threadList; thread != NULL; thread = thread->next) {
        if (thread->threadObj == targetThreadObj)
            break;
    }
    if (thread == NULL) {
        ALOGI("VMStack.getTraceBuf: threadObj %p not active",
            targetThreadObj);
        dvmUnlockThreadList();
        return NULL;
    }

    /*
     * Suspend the thread, pull out the stack trace, then resume the thread
     * and release the thread list lock.  If we're being asked to examine
     * our own stack trace, skip the suspend/resume.
     */
    if (thread != self)
        dvmSuspendThread(thread);
    traceBuf = dvmFillInStackTraceRaw(thread, pStackDepth);
    if (thread != self)
        dvmResumeThread(thread);
    dvmUnlockThreadList();

    return traceBuf;
}

/*
 * public static StackTraceElement[] getThreadStackTrace(Thread t)
 *
 * Retrieve the stack trace of the specified thread and return it as an
 * array of StackTraceElement.  Returns NULL on failure.
 */
static void Dalvik_dalvik_system_VMStack_getThreadStackTrace(const u4* args,
    JValue* pResult)
{
    Object* targetThreadObj = (Object*) args[0];
    size_t stackDepth;
    int* traceBuf = getTraceBuf(targetThreadObj, &stackDepth);

    if (traceBuf == NULL)
        RETURN_PTR(NULL);

    /*
     * Convert the raw buffer into an array of StackTraceElement.
     */
    ArrayObject* trace = dvmGetStackTraceRaw(traceBuf, stackDepth);
    free(traceBuf);
    RETURN_PTR(trace);
}

/*
 * public static int fillStackTraceElements(Thread t, StackTraceElement[] stackTraceElements)
 *
 * Retrieve a partial stack trace of the specified thread and return
 * the number of frames filled.  Returns 0 on failure.
 */
static void Dalvik_dalvik_system_VMStack_fillStackTraceElements(const u4* args,
    JValue* pResult)
{
    Object* targetThreadObj = (Object*) args[0];
    ArrayObject* steArray = (ArrayObject*) args[1];
    size_t stackDepth;
    int* traceBuf = getTraceBuf(targetThreadObj, &stackDepth);

    if (traceBuf == NULL)
        RETURN_PTR(NULL);

    /*
     * Set the raw buffer into an array of StackTraceElement.
     */
    if (stackDepth > steArray->length) {
        stackDepth = steArray->length;
    }
    dvmFillStackTraceElements(traceBuf, stackDepth, steArray);
    free(traceBuf);
    RETURN_INT(stackDepth);
}

const DalvikNativeMethod dvm_dalvik_system_VMStack[] = {
    { "getCallingClassLoader",  "()Ljava/lang/ClassLoader;",
        Dalvik_dalvik_system_VMStack_getCallingClassLoader },
    { "getStackClass2",         "()Ljava/lang/Class;",
        Dalvik_dalvik_system_VMStack_getStackClass2 },
    { "getClasses",             "(I)[Ljava/lang/Class;",
        Dalvik_dalvik_system_VMStack_getClasses },
    { "getThreadStackTrace",    "(Ljava/lang/Thread;)[Ljava/lang/StackTraceElement;",
        Dalvik_dalvik_system_VMStack_getThreadStackTrace },
    { "fillStackTraceElements", "(Ljava/lang/Thread;[Ljava/lang/StackTraceElement;)I",
        Dalvik_dalvik_system_VMStack_fillStackTraceElements },
    { NULL, NULL, NULL },
};