0

I've been working C++ libraries in Java by JNI.

OS: Ubuntu 16.04

code :

JNIEXPORT jint JNICALL Java_FocasUser_Focas2_getMacroDataArray
(JNIEnv *env, jobject instance, jint handle, jint startIndex, jint endIndex, jdoubleArray jArr) {

    int len = env->GetArrayLength(jArr);
    IODBMR *macro;
    macro = (IODBMR *) malloc(8 + (8 * len));

    clock_t before = clock();
    if (PrintLogCat) LOGGER.debug("cnc_rdmacror.. start Index: [%d]\t endIndex:[%d]\t",1);
    short ret = cnc_rdmacror(handle, startIndex, endIndex, 8 + (8 * len), macro);

    int elapsedTime = calcElapsedTime(before, clock());
    loggerStream << "[" << "cnc_rdmacror" << "]=" << ret << "\tElapsed Time ms : " << elapsedTime;
    fileLogger(loggerStream.str(), LOG_FILE_WRITE_COND(elapsedTime));
    
    loggerStream.str("");

    jdouble *arr = env->GetDoubleArrayElements(jArr, 0);
    double temp = 0;

    if (ret == EW_OK) {

        for (int idx = 0; idx <= endIndex - startIndex; idx++) {
            temp = macro->data[idx].mcr_val;
            for (int i = 0; i < macro->data[idx].dec_val; i++) {
                temp = temp / 10;
            }
            arr[idx] = temp;
        }
    } else {

        env->ReleaseDoubleArrayElements(jArr, arr, 0);

        free(macro);
        free(arr);
        macro = NULL;
        arr = NULL;
        env->DeleteLocalRef(instance);
        if (PrintLogCat) LOGGER.error("getMacroValue pmc error : %d", ret,1);
        return -1;
    }

    env->ReleaseDoubleArrayElements(jArr, arr, 0);


    free(macro);
    free(arr);

    arr = NULL;
    macro = NULL;
    
    env->DeleteLocalRef(instance);

    return 0;
}

"double free or corruption" occurs at the line "free(arr)". I initialize before "free(arr)" then it was okay but I'm not sure if it's fine or not. How can I solve it?

Thank you really much in advance,

Log : https://www.evernote.com/l/AtWkcucPzCZMN4b61B9rvNLCZazK95mk3Eo/

이지욱
  • 1
  • 1
  • 3
    Get rid of the manual memory management by using `std::vector` instead of `malloc`. Then none of those calls to `free` would need to be there, messing things up. As a matter of fact, for sound JNI programming, it is preferable to use RAII objects such as `std::vector` instead of manual memory management. The code becomes much simpler, easier to maintain, and practically eliminates the issue of memory leaks. – PaulMcKenzie Jul 09 '21 at 04:45
  • 2
    You already released the array pointed to by `arr` using `ReleaseDoubleArrayElements`. What makes you think you need to `free` it as well? As a general rule, if you didn't get something from `malloc` you shouldn't `free` it (unless the relevant documentation explicitly says you should). – Miles Budnek Jul 09 '21 at 04:45
  • 1
    You might consider (if on Linux) using [valgrind](http://valgrind.org/); compile most code with debug information, so using `g++ -Wall -Wextra -g -O` – Basile Starynkevitch Jul 09 '21 at 05:07

1 Answers1

2

As mentioned in the comments, the function becomes much simpler by using std::vector instead of manual memory management using malloc and free. Not only simpler, but the possibility of memory leaks becomes minimized, if not totally eliminated.

In addition, arr was not allocated using malloc, thus calling free(arr); is incorrect.

Here is your function rewritten (not tested), that totally eliminates all the calls to free():

#include <vector>
//... 
struct LocalRefHandler
{
    jobject *m_pObject;
    JNIEnv *m_pEnv; 

    LocalRefHandler(JNIEnv* pEnv, jobject* instance) : 
                      m_pEnv(pEnv), m_pObject(instance) {}

    // Release the resources  
    ~LocalRefHandler() { m_pEnv->DeleteLocalRef(*m_pObject); }
};

JNIEXPORT jint JNICALL Java_FocasUser_Focas2_getMacroDataArray
(JNIEnv *env, jobject instance, jint handle, jint startIndex, jint endIndex, jdoubleArray jArr) {

    // This will automatically call DeleteLocalRef when the function exits 
    LocalRefHandler refHandler(env, &instance); 

    int len = env->GetArrayLength(jArr);

    // This will clean up the memory for us when the function returns 
    std::vector<char> vc(8 + (8 * len));

    // point to the underlying data
    IODMBR* macro = static_cast<IODMBR*>(vc.data());

    clock_t before = clock();
    if (PrintLogCat) LOGGER.debug("cnc_rdmacror.. start Index: [%d]\t endIndex:[%d]\t",1);
    short ret = cnc_rdmacror(handle, startIndex, endIndex, 8 + (8 * len), macro);

    int elapsedTime = calcElapsedTime(before, clock());
    loggerStream << "[" << "cnc_rdmacror" << "]=" << ret << "\tElapsed Time ms : " << elapsedTime;
    fileLogger(loggerStream.str(), LOG_FILE_WRITE_COND(elapsedTime));
    
    loggerStream.str("");

    jdouble *arr = env->GetDoubleArrayElements(jArr, 0);
    double temp = 0;

    if (ret == EW_OK) {
        for (int idx = 0; idx <= endIndex - startIndex; idx++) {
            temp = macro->data[idx].mcr_val;
            for (int i = 0; i < macro->data[idx].dec_val; i++) {
                temp = temp / 10;
            }
            arr[idx] = temp;
        }
    } else {
        env->ReleaseDoubleArrayElements(jArr, arr, 0);
        if (PrintLogCat) LOGGER.error("getMacroValue pmc error : %d", ret,1);
        return -1;
    }

    env->ReleaseDoubleArrayElements(jArr, arr, 0);
    return 0;
}

The std::vector will automatically release the memory on function return, regardless of the reason or where the function returns.

The reason why this is important is that regardless of where the function returns, the std::vector will "free" the memory automatically. This includes if an exception is thrown for some reason. So not only do you not have to worry about multiple return points, even if an exception is thrown, the vector will deallocate the memory.

This method of programming, namely RAII, is essential if you're going to use JNI.

Since the possibility is somewhat high of exceptions being thrown, it is important that any resources acquired (such as allocated memory, file handles, various JNI structures, etc.) must be cleaned up on exit of the JNI function. Using objects that automatically clean themselves up on destruction is much easier to deal with than multiple try/catch blocks, where you're cleaning up things manually.

As an example, you are calling env->DeleteLocalRef(instance); in multiple places. What if an exception is thrown, and you do not get a chance for the env->DeleteLocalRef to be invoked? JNI programming is the prime example of where RAII techniques are almost a must.

Note that a LocalRefHandler is a struct that was created that when an object of this type is destroyed, the DeleteLocalRef is called. Once the refHandler variable is created from the JNI environment pointer and the instance, you can see that we no longer need to add DeleteLocalRef calls sprinkled within the code at the return points -- DeleteLocalRef will be called regardless of the reason the function exits once refHandler goes out of scope.

You could also create a similar class to handle the env->GetDoubleArrayElements and env->ReleaseDoubleArrayElements, so that you are cleaning up those resources automatically on function exit.

The bottom line is that you should build up your own codebase of these small RAII objects for JNI purposes, so that they are readily available to be used.

PaulMcKenzie
  • 34,698
  • 4
  • 24
  • 45
  • `IODMBR* macro = static_cast(vc.data());` should be a `reinterpret_cast` instead, right? Also, note, that this operation was Undefined Behaviour until p0593 (implicit creation of objects for low-level manipulation) got applied. A much safer way would be to create a `std::vector` of correct length, and then pass `sizeof(IODMBR*) * @some vectorname@.size()` as buffer size to `cnc_rdmacror` – Kaznov Jul 09 '21 at 06:58