views:

258

answers:

2

Hi everyone, I'm getting weird string corruption across JNI calls which is causing problems on the the Java side. Every so often, I'll get a corrupted string in the passed array, which sometimes has existing parts of the original non-corrupted string. The C++ code is supposed to set the first index of the array to the address, it's a nasty hack to get around method call limitations. Additionally, the application is multi-threaded.

remoteaddress[0]: 10.1.1.2:49153
remoteaddress[0]: 10.1.4.2:49153
remoteaddress[0]: 10.1.6.2:49153
remoteaddress[0]: 10.1.2.2:49153
remoteaddress[0]: 10.1.9.2:49153
remoteaddress[0]: {garbage here}
java.lang.NullPointerException
    at kokuks.KKSAddress.<init>(KKSAddress.java:139)
    at kokuks.KKSAddress.createAddress(KKSAddress.java:48)
    at kokuks.KKSSocket._recvFrom(KKSSocket.java:963)
    at kokuks.scheduler.RecvOperation$1.execute(RecvOperation.java:144)
    at kokuks.scheduler.RecvOperation$1.execute(RecvOperation.java:1)
    at kokuks.KKSEvent.run(KKSEvent.java:58)
    at kokuks.KokuKS.handleJNIEventExpiry(KokuKS.java:872)
    at kokuks.KokuKS.handleJNIEventExpiry_fjni(KokuKS.java:880)
    at kokuks.KokuKS.runSimulator_jni(Native Method)
    at kokuks.KokuKS$1.run(KokuKS.java:773)
    at java.lang.Thread.run(Thread.java:717)
remoteaddress[0]: 10.1.7.2:49153

The null pointer exception comes from trying to use the corrupt string. In C++, the address prints to standard out normally, but doing this reduces the rate of errors, from what I can see.

The C++ code (if it helps):

/*
 * Class:     kokuks_KKSSocket
 * Method:    recvFrom_jni
 * Signature: (Ljava/lang/String;[Ljava/lang/String;Ljava/nio/ByteBuffer;IIJ)I
 */
JNIEXPORT jint JNICALL Java_kokuks_KKSSocket_recvFrom_1jni
(JNIEnv *env, jobject obj, jstring sockpath, jobjectArray addrarr, jobject buf, jint position, jint limit, jlong flags) {
    if (addrarr && env->GetArrayLength(addrarr) > 0) {
        env->SetObjectArrayElement(addrarr, 0, NULL);
    }

    jboolean iscopy;
    const char* cstr = env->GetStringUTFChars(sockpath, &iscopy);
    std::string spath = std::string(cstr);
    env->ReleaseStringUTFChars(sockpath, cstr); // release me!

    if (KKS_DEBUG) {
        std::cout << "[kks-c~" << spath << "] " << __PRETTY_FUNCTION__ << std::endl;
    }

    ns3::Ptr<ns3::Socket> socket = ns3::Names::Find<ns3::Socket>(spath);
    if (!socket) {
        std::cout << "[kks-c~" << spath << "] " << __PRETTY_FUNCTION__ << " socket not found for path!!" << std::endl;
        return -1; // not found
    }

    if (!addrarr) {
        std::cout << "[kks-c~" << spath << "] " << __PRETTY_FUNCTION__ << " array to set sender is null" << std::endl;
        return -1;      
    }

    jsize arrsize = env->GetArrayLength(addrarr);
    if (arrsize < 1) {
        std::cout << "[kks-c~" << spath << "] " << __PRETTY_FUNCTION__ << " array too small to set sender!" << std::endl;
        return -1;
    }

    uint8_t* bufaddr = (uint8_t*)env->GetDirectBufferAddress(buf);
    long bufcap = env->GetDirectBufferCapacity(buf);
    uint8_t* realbufaddr = bufaddr + position;
    uint32_t remaining = limit - position;

    if (KKS_DEBUG) {
        std::cout << "[kks-c~" << spath << "] " << __PRETTY_FUNCTION__ << " bufaddr: " << bufaddr << ", cap: " << bufcap << std::endl;
    }

    ns3::Address aaddr;
    uint32_t mflags = flags;

    int ret = socket->RecvFrom(realbufaddr, remaining, mflags, aaddr);

    if (ret > 0) {
        if (KKS_DEBUG) std::cout << "[kks-c~" << spath << "] " << __PRETTY_FUNCTION__ << " addr: " << aaddr << std::endl;
        ns3::InetSocketAddress insa = ns3::InetSocketAddress::ConvertFrom(aaddr);

        std::stringstream ss;
        insa.GetIpv4().Print(ss);
        ss << ":" << insa.GetPort() << std::ends;

        if (KKS_DEBUG) std::cout << "[kks-c~" << spath << "] " << __PRETTY_FUNCTION__ << " addr: " << ss.str() << std::endl;

        jsize index = 0;
        const char *cstr = ss.str().c_str();
        jstring jaddr = env->NewStringUTF(cstr);
        if (jaddr == NULL) std::cout << "[kks-c~" << spath << "] " << __PRETTY_FUNCTION__ << " jaddr is null!!" << std::endl;

        //jaddr = (jstring)env->NewGlobalRef(jaddr);
        env->SetObjectArrayElement(addrarr, index, jaddr);

        //if (env->ExceptionOccurred()) {
        //  env->ExceptionDescribe();
        //}
    }

    jint jret = ret;
    return jret;
}

The Java code (if it helps):

/**
 * Pass an array of size 1 into remote address, and this will be set with
 * the sender of the packet (hax). This emulates C++ references.
 * 
 * @param remoteaddress
 * @param buf
 * @param flags
 * @return
 */
public int _recvFrom(final KKSAddress remoteaddress[], ByteBuffer buf, long flags) {
    if (!kks.isCurrentlyThreadSafe()) throw new RuntimeException(
        "Not currently thread safe for ns-3 functions!"
    );

    //lock.lock();
    try {
        if (!buf.isDirect()) return -6; // not direct!!

        final String[] remoteAddrStr = new String[1];

        int ret = 0;

        ret = recvFrom_jni(
            path.toPortableString(),
            remoteAddrStr,
            buf,
            buf.position(),
            buf.limit(),
            flags
        );
        if (ret > 0) {
            System.out.println("remoteaddress[0]: " + remoteAddrStr[0]);
            remoteaddress[0] = KKSAddress.createAddress(remoteAddrStr[0]);
            buf.position(buf.position() + ret);
        }

        return ret;
    } finally {
        errNo = _getErrNo();
        //lock.unlock();
    }
}

public int recvFrom(KKSAddress[] fromaddress, final ByteBuffer bytes, long flags, long timeoutMS) {
    if (KokuKS.DEBUG_MODE) printMessage("public synchronized int recvFrom(KKSAddress[] fromaddress, final ByteBuffer bytes, long flags, long timeoutMS)");

    if (kks.isCurrentlyThreadSafe()) {
        return _recvFrom(fromaddress, bytes, flags); // avoid event
    }

    fromaddress[0] = null;

    RecvOperation ro = new RecvOperation(
        kks,
        this,
        flags,
        true,
        bytes,
        timeoutMS
    );
    ro.start();
    fromaddress[0] = ro.getFrom();
    return ro.getRetCode();
}
A: 

I haven't tested your code but I am assuming it to be a character encoding issue. Try using std::wstring instead of std::string or something like that. Sorry can't help much at this moment, but this should give you a starting point, I guess.

6pack kid
Not sure, why would it give me garbage strings every so often rather than all the time? Perhaps it's a memory allocation issue. Very odd.
Chris Dennett
A: 

Couldn´t be thread-safetiness issues? Try to synchronize the access to the native methods.

Juliano
I think this does sort it out, but isn't JNI supposed to be thread safe?
Chris Dennett
I really don't know if JNI is thread safe, but if the C++ DLL is not, the whole solution will not be thread safe.
Juliano
Hmm, well, the C++ DLL should be thread safe, there's a mutex on the event functions contained within which are called from the Java side. I suppose that only leaves the JNI mechanism which could have the problem.
Chris Dennett