/*
 * Copyright (c) 1999, 2005, Oracle and/or its affiliates.  All rights reserved. 
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.   Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code. 
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.   See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code). 
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions. 
 */

#include <new.h>
#include <stdio.h>
#include "awt_new.h"
#include "awt_Toolkit.h"
#include "Hashtable.h"

// Don't want to pull in the redefined allocation functions
#undef malloc
#undef calloc
#undef realloc
#undef ExceptionOccurred

#ifdef OUTOFMEM_TEST
  #undef safe_Malloc
  #undef safe_Calloc
  #undef safe_Realloc
  #undef new

  static CriticalSection *alloc_lock;
  static FILE *logfile;
  static DWORD thread_seeded = TLS_OUT_OF_INDEXES;
#endif


void
NewHandler::init() {
    JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);

#ifdef OUTOFMEM_TEST
    alloc_lock = new CriticalSection();
    logfile = fopen("java.awt.outofmem.txt", "w");
    DASSERT(logfile);
    thread_seeded = TlsAlloc();
    DASSERT(thread_seeded ! = TLS_OUT_OF_INDEXES);
#endif

    // use new handler for operator new and malloc
    _set_new_mode(1);

    // set the function which will be called when operator new or
    // malloc runs out of memory
    _set_new_handler((_PNH)NewHandler::handler);
}

// Called when malloc or operator new runs out of memory.  We try to
// compact the heap by initiating a Java GC.  If the amount of free
// memory available after this operation increases, then we return
// (1) to indicate that malloc or operator new should retry the
// allocation.  Returning (0) indicates that the allocation should fail. 
int
NewHandler::handler(size_t) {
    fprintf(stderr, "java.lang. OutOfMemoryError\n");
    return FALSE;
}

// These three functions throw std::bad_alloc in an out of memory condition
// instead of returning 0.  safe_Realloc will return 0 if memblock is not
// NULL and size is 0.  safe_Malloc and safe_Calloc will never return 0. 
void *safe_Malloc(size_t size) throw (std::bad_alloc) {
    register void *ret_val = malloc(size);
    if (ret_val == NULL) {
        throw std::bad_alloc();
    }

    return ret_val;
}

void *safe_Calloc(size_t num, size_t size) throw (std::bad_alloc) {
    register void *ret_val = calloc(num, size);
    if (ret_val == NULL) {
        throw std::bad_alloc();
    }

    return ret_val;
}

void *safe_Realloc(void *memblock, size_t size) throw (std::bad_alloc) {
    register void *ret_val = realloc(memblock, size);

    // Special case for realloc. 
    if (memblock ! = NULL && size == 0) {
        return ret_val; // even if it's NULL
    }

    if (ret_val == NULL) {
        throw std::bad_alloc();
    }

    return ret_val;
}

#if ! defined(DEBUG)
// This function exists because VC++ 5.0 currently does not conform to the
// Standard C++ specification which requires that operator new throw
// std::bad_alloc in an out of memory situation.  Instead, VC++ 5.0 returns 0. 
//
// This function can be safely removed when the problem is corrected. 
void * CDECL operator new(size_t size) throw (std::bad_alloc) {
    return safe_Malloc(size);
}
#endif

// This function is called at the beginning of an entry point. 
// Entry points are functions which are declared:
//   1.  CALLBACK,
//   2.  JNIEXPORT,
//   3. __ declspec(dllexport), or
//   4.  extern "C"
// A function which returns an HRESULT (an OLE function) is also an entry
// point. 
void
entry_point(void) {
    if (jvm ! = NULL) {
        JNIEnv* env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);
        if (env ! = NULL) {
            env->ExceptionClear();
        }
    }
}


// This function is called when a std::bad_alloc exception is caught. 
void
handle_bad_alloc(void) {
    if (jvm ! = NULL) {
        JNIEnv* env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);
        if (env ! = NULL && ! env->ExceptionCheck()) {
            JNU_ThrowOutOfMemoryError(env, "OutOfMemoryError");
        }
    }
}


// This function is called instead of ExceptionOccurred.  It throws
// std::bad_alloc if a java.lang. OutOfMemoryError is currently pending
// on the calling thread. 
jthrowable
safe_ExceptionOccurred(JNIEnv *env) throw (std::bad_alloc) {
    jthrowable xcp = env->ExceptionOccurred();
    if (xcp ! = NULL) {
        env->ExceptionClear(); // if we don't do this, isInstanceOf will fail
        jint isOutofmem = JNU_IsInstanceOfByName(env, xcp, "java/lang/OutOfMemoryError");
        if (isOutofmem > 0) {
            env->DeleteLocalRef(xcp);
            throw std::bad_alloc();
        } else {
            env->ExceptionClear();
            // rethrow exception
            env->Throw(xcp);
            // temp solution to reveal all concurrency issues in jtreg and JCK
            // we will switch it back to silent mode before the release
            env->ExceptionDescribe();
            return xcp;
        }
    }

    return NULL;
}

#ifdef OUTOFMEM_TEST

#include <time.h>
#include <limits.h>

static void
rand_alloc_fail(const char *file, int line) throw (std::bad_alloc)
{
    if (alloc_lock == NULL) { // Not yet initialized
        return;
    }

    CriticalSection::Lock l(*alloc_lock);

    // Each thread must be seeded individually
    if (! TlsGetValue(thread_seeded)) {
        TlsSetValue(thread_seeded, (LPVOID)1);
        srand((unsigned int)time(NULL));
    }

    if (rand() > (int)(RAND_MAX * .999)) { // .1% chance of alloc failure
        fprintf(stderr, "failing allocation at %s, %d\n", file, line);
        fprintf(logfile, "%s, %d\n", file, line);
        fflush(logfile);

        VERIFY(malloc(INT_MAX) == 0); // should fail

        throw std::bad_alloc();
    }
}

void *safe_Malloc_outofmem(size_t size, const char *file, int line)
    throw (std::bad_alloc)
{
    rand_alloc_fail(file, line);
    return safe_Malloc(size);
}

void *safe_Calloc_outofmem(size_t num, size_t size, const char *file, int line)
    throw (std::bad_alloc)
{
    rand_alloc_fail(file, line);
    return safe_Calloc(num, size);
}

void *safe_Realloc_outofmem(void *memblock, size_t size, const char *file,
                            int line)
    throw (std::bad_alloc)
{
    rand_alloc_fail(file, line);
    return safe_Realloc(memblock, size);
}

void * CDECL operator new(size_t size, const char *file, int line)
    throw (std::bad_alloc)
{
    rand_alloc_fail(file, line);
    return operator new(size);
}

#endif
