Fix JNI argv parsing: throw exception on NULL

Add new 'jni' test case to ensure this works
This commit is contained in:
Andrew Bettison 2013-02-15 19:23:34 +10:30
parent e856cafc97
commit 788b3e37b2
3 changed files with 43 additions and 20 deletions

View File

@ -117,7 +117,8 @@ static int outv_end_field()
return put_blob((jbyte *)outv_buffer, length); return put_blob((jbyte *)outv_buffer, length);
} }
int Throw(JNIEnv *env, const char *class, const char *msg){ int Throw(JNIEnv *env, const char *class, const char *msg)
{
jclass exceptionClass = NULL; jclass exceptionClass = NULL;
if ((exceptionClass = (*env)->FindClass(env, class)) == NULL) if ((exceptionClass = (*env)->FindClass(env, class)) == NULL)
return -1; // exception return -1; // exception
@ -157,40 +158,36 @@ JNIEXPORT jint JNICALL Java_org_servalproject_servald_ServalD_rawCommand(JNIEnv
return Throw(env, "java/lang/IllegalStateException", "Unable to locate method totalRowCount"); return Throw(env, "java/lang/IllegalStateException", "Unable to locate method totalRowCount");
} }
unsigned char status = 0; // to match what the shell gets: 0..255 unsigned char status = 0; // to match what the shell gets: 0..255
if (jni_env) if (jni_env)
return Throw(env, "java/lang/IllegalStateException", "re-entrancy not supported"); return Throw(env, "java/lang/IllegalStateException", "re-entrancy not supported");
// Construct argv, argc from this method's arguments. // Construct argv, argc from this method's arguments.
jsize len = (*env)->GetArrayLength(env, args); jsize len = (*env)->GetArrayLength(env, args);
const char **argv = alloca(sizeof(char*) * (len + 1)); const char **argv = alloca(sizeof(char*) * (len + 1));
if (argv == NULL) if (argv == NULL)
return Throw(env, "java/lang/OutOfMemoryError", "alloca returned NULL"); return Throw(env, "java/lang/OutOfMemoryError", "alloca() returned NULL");
argv[len]=NULL;
jsize i; jsize i;
for (i = 0; i <= len; ++i)
argv[i] = NULL;
int argc = len; int argc = len;
// From now on, in case of an exception we have to free some resources before // From now on, in case of an exception we have to free some resources before
// returning. // returning.
jni_exception = 0; jni_exception = 0;
const char *empty=""; for (i = 0; !jni_exception && i < len; ++i) {
for (i = 0; i < len; ++i) {
const jstring arg = (jstring)(*env)->GetObjectArrayElement(env, args, i); const jstring arg = (jstring)(*env)->GetObjectArrayElement(env, args, i);
if ((*env)->ExceptionOccurred(env)) if ((*env)->ExceptionOccurred(env))
jni_exception = 1; jni_exception = 1;
else if (arg == NULL) {
argv[i] = empty; Throw(env, "java/lang/NullPointerException", "null element in argv");
if (arg != NULL && !jni_exception){ jni_exception = 1;
}
else {
const char *str = (*env)->GetStringUTFChars(env, arg, NULL); const char *str = (*env)->GetStringUTFChars(env, arg, NULL);
if (str == NULL){ if (str == NULL)
jni_exception = 1; jni_exception = 1;
}else else
argv[i] = str; argv[i] = str;
} }
} }
if (!jni_exception) { if (!jni_exception) {
// Set up the output buffer. // Set up the output buffer.
jniResults = outv; jniResults = outv;
@ -200,10 +197,9 @@ JNIEXPORT jint JNICALL Java_org_servalproject_servald_ServalD_rawCommand(JNIEnv
status = parseCommandLine(NULL, argc, argv); status = parseCommandLine(NULL, argc, argv);
jni_env = NULL; jni_env = NULL;
} }
// Release argv Java string buffers. // Release argv Java string buffers.
for (i = 0; i < len; ++i) { for (i = 0; i < len; ++i) {
if (argv[i] && argv[i]!=empty) { if (argv[i]) {
const jstring arg = (jstring)(*env)->GetObjectArrayElement(env, args, i); const jstring arg = (jstring)(*env)->GetObjectArrayElement(env, args, i);
(*env)->ReleaseStringUTFChars(env, arg, argv[i]); (*env)->ReleaseStringUTFChars(env, arg, argv[i]);
} }

View File

@ -34,5 +34,23 @@ class ServalDTests
} }
System.out.println(""); System.out.println("");
} }
System.exit(0);
} }
public static void nullArg(String[] args)
{
ServalD servald = new ServalD();
for (int i = 0; i != args.length; ++i)
if ("(null)".equals(args[i]))
args[i] = null;
servald.command(Arrays.copyOfRange(args, 0, args.length));
System.out.print(servald.status);
for (byte[] a: servald.outv) {
System.out.print(":");
System.out.print(new String(a));
}
System.out.println("");
System.exit(0);
}
} }

View File

@ -47,7 +47,7 @@ assert_echo_works() {
doc_Echo="Serval JNI echo Hello world" doc_Echo="Serval JNI echo Hello world"
test_Echo() { test_Echo() {
executeOk java "-Djava.library.path=$LD_LIBRARY_PATH" -classpath "$PWD/classes" org.servalproject.servald.ServalD echo -e 'Hello,\ttab' 'world\0!' executeOk java "-Djava.library.path=$LD_LIBRARY_PATH" -classpath "$PWD/classes" org.servalproject.servald.ServalD 'echo' '-e' 'Hello,\ttab' 'world\0!'
assertStdoutIs -e 'Hello,\ttab\nworld\0!\n' assertStdoutIs -e 'Hello,\ttab\nworld\0!\n'
} }
@ -64,9 +64,18 @@ test_Delim() {
doc_Repeat="Serval JNI repeated calls in same process" doc_Repeat="Serval JNI repeated calls in same process"
test_Repeat() { test_Repeat() {
executeOk java "-Djava.library.path=$LD_LIBRARY_PATH" -classpath "$PWD/classes" org.servalproject.servald.ServalDTests repeat 50 echo 'Hello,' 'world!' executeOk --core-backtrace java "-Djava.library.path=$LD_LIBRARY_PATH" -classpath "$PWD/classes" org.servalproject.servald.ServalDTests repeat 50 'echo' 'Hello,' 'world!'
assertStdoutLineCount '==' 50 assertStdoutLineCount '==' 50
assertStdoutGrep --matches=50 '^0:Hello,:world!$' assertStdoutGrep --matches=50 '^0:Hello,:world!$'
} }
doc_NullArg="Serval JNI null arguments throw exception"
test_NullArg() {
execute --core-backtrace java "-Djava.library.path=$LD_LIBRARY_PATH" -classpath "$PWD/classes" org.servalproject.servald.ServalDTests nullArg 'echo' '(null)'
tfw_cat --stdout --stderr
assertExitStatus '!=' 0
assertStderrGrep 'NullPointerException: null element in argv'
}
runTests "$@" runTests "$@"