Search code examples
javaandroidc++java-native-interface

Is it ever possible to "trick" a Java program into mistaking any string for null?


This is an interview question:

Suppose you are coding for an Android system which utilizes JNI and some C++ code to read data from a serial interface (uart, for example).

The bottom level stuff will be taken care of by the C++ code, and eventually data will be fed to a Java interface where it will be treated as the type String.

The question is: suppose the Java code handling the reading goes something like this:

private void parseSerialData(String input){

 if (input==null){
//DO SOMETHING HERE
    }
}

Will it ever be possible for the conditions of the if block be met?

My understanding of Java tells me it is never going to be possible because (I could be thoroughly wrong) null in Java is way to denominate "there is no reference", null isn't an object, and it wasn't instantiated from a class. At the DVM or JVM level, as long as a variable was declared, a reference was made, even though there may be no memory allocated for it on the stack.

Therefore it is impossible for a local variable as a parameter of a method to not have a reference to begin with, not to mention it was given a reference later on which points at a String object (even though this object may have no information with it, it is nevertheless, a non-null object) and as such, the if condition will never be met.

But I couldn't decide if this question is a trick question and I have omitted something? Especially considering that there's C++ in the fray, and I have no idea what it is like to pass a C++ null reference to Java, if it makes sense at all?

So, will the if condition be met, ever?


Solution

  • Yes, of course input can be null. It simply depends on what the C++ code does. Here is a complete example. It wasn't clear from your description whether the native method returns the String or passes it as a parameter from a native method to a java method, so I included an example of each.

    This program calls f five times. The first three times simply pass a constant value. The following two times wait for input (in a native method) and pass either null or non-null to f depending on whether the input is empty or not.

    Test.java

    public class Test
    {
        static {
            System.loadLibrary("Test");
        }
    
        public static void main(String [] args)
        {
            Test obj = new Test();
            obj.main();
        }
    
        void main()
        {
            f(null);
            f("not null");
            String s1 = null;
            f(s1);
            String s2 = n1();
            f(s2);
            n2();
        }
    
        public void f(String s)
        {
            if (s == null)
                System.out.println("null");
            else
                System.out.println(s);
        }
    
        native String n1();
        native void n2();
    }
    

    Test.cc

    #include <stdio.h>
    #include <jni.h>
    #include "Test.h"
    
    static jstring get_string(JNIEnv *env);
    
    JNIEXPORT jstring JNICALL Java_Test_n1(JNIEnv *env, jobject obj)
    {
        return get_string(env);
    }
    
    JNIEXPORT void JNICALL Java_Test_n2(JNIEnv *env, jobject obj)
    {
        jstring s = get_string(env);
        jclass cls = env->GetObjectClass(obj);
        jmethodID f = env->GetMethodID(cls, "f", "(Ljava/lang/String;)V");
        env->CallVoidMethod(obj, f, s);
    }
    
    static jstring get_string(JNIEnv *env)
    {
        char buf[20];
        if (fgets(buf, sizeof buf, stdin) == NULL)
            return NULL;
        if (buf[0] == '\n')
            return NULL;
        return env->NewStringUTF(buf);
    }
    

    Build instructions

    $ javac Test.java
    $ javah Test
    $ gcc --std=c++11 -I $JAVA_HOME/include -I $JAVA_HOME/include/linux  -fPIC -shared -o libTest.so Test.cc
    $ /usr/bin/java -Djava.library.path=. Test