Skip to content

Commit b366380

Browse files
committed
Add jniThrowErrnoException in libnativehelper
Since a few places in frameworks and libcore implement their own functions to throw android.system.ErrnoException, these have been integrated in libnativehelper to avoid duplication. As part of the change a more generic ThrowException function is provided that can take a variable number of arguments, rather than just a description string. Due to this, the code now constructs the exception object itself and then passes it to the JNIEnv. A helper macro is also added to convert the C string to jstring for the description parameter, which is usually the first parameter in the exception constructors. Bug: 180958753 Fix: 180958753 Test: atest MtsLibnativehelperTestCases Test: atest MtsLibnativehelperLazyTestCases Change-Id: I4841cf9924a48eaf0b6965c203c9ebe851e443ff
1 parent 2cb6274 commit b366380

File tree

7 files changed

+133
-23
lines changed

7 files changed

+133
-23
lines changed

JNIHelp.c

+87-20
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include "include/nativehelper/JNIHelp.h"
1818

19+
#include <stdarg.h>
1920
#include <stdbool.h>
2021
#include <stdio.h>
2122
#include <stdlib.h>
@@ -233,6 +234,84 @@ static void DiscardPendingException(JNIEnv* env, const char* className) {
233234
(*env)->DeleteLocalRef(env, exception);
234235
}
235236

237+
static int ThrowException(JNIEnv* env, const char* className, const char* ctorSig, ...) {
238+
int status = -1;
239+
jclass exceptionClass = NULL;
240+
241+
va_list args;
242+
va_start(args, ctorSig);
243+
244+
DiscardPendingException(env, className);
245+
246+
{
247+
/* We want to clean up local references before returning from this function, so,
248+
* regardless of return status, the end block must run. Have the work done in a
249+
* nested block to avoid using any uninitialized variables in the end block. */
250+
exceptionClass = (*env)->FindClass(env, className);
251+
if (exceptionClass == NULL) {
252+
ALOGE("Unable to find exception class %s", className);
253+
/* an exception, most likely ClassNotFoundException, will now be pending */
254+
goto end;
255+
}
256+
257+
jmethodID init = (*env)->GetMethodID(env, exceptionClass, "<init>", ctorSig);
258+
if(init == NULL) {
259+
ALOGE("Failed to find constructor for '%s' '%s'", className, ctorSig);
260+
goto end;
261+
}
262+
263+
jobject instance = (*env)->NewObjectV(env, exceptionClass, init, args);
264+
if (instance == NULL) {
265+
ALOGE("Failed to construct '%s'", className);
266+
goto end;
267+
}
268+
269+
if ((*env)->Throw(env, (jthrowable)instance) != JNI_OK) {
270+
ALOGE("Failed to throw '%s'", className);
271+
/* an exception, most likely OOM, will now be pending */
272+
goto end;
273+
}
274+
275+
/* everything worked fine, just update status to success and clean up */
276+
status = 0;
277+
}
278+
279+
end:
280+
va_end(args);
281+
if (exceptionClass != NULL) {
282+
(*env)->DeleteLocalRef(env, exceptionClass);
283+
}
284+
return status;
285+
}
286+
287+
static jstring CreateExceptionMsg(JNIEnv* env, const char* msg) {
288+
jstring detailMessage = (*env)->NewStringUTF(env, msg);
289+
if (detailMessage == NULL) {
290+
/* Not really much we can do here. We're probably dead in the water,
291+
but let's try to stumble on... */
292+
(*env)->ExceptionClear(env);
293+
}
294+
return detailMessage;
295+
}
296+
297+
/* Helper macro to deal with conversion of the exception message from a C string
298+
* to jstring.
299+
*
300+
* This is useful because most exceptions have a message as the first parameter
301+
* and delegating the conversion to all the callers of ThrowException results in
302+
* code duplication. However, since we try to allow variable number of arguments
303+
* for the exception constructor we'd either need to do the conversion inside
304+
* the macro, or manipulate the va_list to replace the C string to a jstring.
305+
* This seems like the cleaner solution.
306+
*/
307+
#define THROW_EXCEPTION_WITH_MESSAGE(env, className, ctorSig, msg, ...) ({ \
308+
jstring _detailMessage = CreateExceptionMsg(env, msg); \
309+
int _status = ThrowException(env, className, ctorSig, _detailMessage, ## __VA_ARGS__); \
310+
if (_detailMessage != NULL) { \
311+
(*env)->DeleteLocalRef(env, _detailMessage); \
312+
} \
313+
_status; })
314+
236315
//
237316
// JNIHelp external API
238317
//
@@ -277,24 +356,7 @@ void jniLogException(JNIEnv* env, int priority, const char* tag, jthrowable thro
277356
}
278357

279358
int jniThrowException(JNIEnv* env, const char* className, const char* message) {
280-
DiscardPendingException(env, className);
281-
282-
jclass exceptionClass = (*env)->FindClass(env, className);
283-
if (exceptionClass == NULL) {
284-
ALOGE("Unable to find exception class %s", className);
285-
/* ClassNotFoundException now pending */
286-
return -1;
287-
}
288-
289-
int status = 0;
290-
if ((*env)->ThrowNew(env, exceptionClass, message) != JNI_OK) {
291-
ALOGE("Failed throwing '%s' '%s'", className, message);
292-
/* an exception, most likely OOM, will now be pending */
293-
status = -1;
294-
}
295-
(*env)->DeleteLocalRef(env, exceptionClass);
296-
297-
return status;
359+
return THROW_EXCEPTION_WITH_MESSAGE(env, className, "(Ljava/lang/String;)V", message);
298360
}
299361

300362
int jniThrowExceptionFmt(JNIEnv* env, const char* className, const char* fmt, va_list args) {
@@ -311,12 +373,17 @@ int jniThrowRuntimeException(JNIEnv* env, const char* msg) {
311373
return jniThrowException(env, "java/lang/RuntimeException", msg);
312374
}
313375

314-
int jniThrowIOException(JNIEnv* env, int errnum) {
376+
int jniThrowIOException(JNIEnv* env, int errno_value) {
315377
char buffer[80];
316-
const char* message = platformStrError(errnum, buffer, sizeof(buffer));
378+
const char* message = platformStrError(errno_value, buffer, sizeof(buffer));
317379
return jniThrowException(env, "java/io/IOException", message);
318380
}
319381

382+
int jniThrowErrnoException(JNIEnv* env, const char* functionName, int errno_value) {
383+
return THROW_EXCEPTION_WITH_MESSAGE(env, "android/system/ErrnoException",
384+
"(Ljava/lang/String;I)V", functionName, errno_value);
385+
}
386+
320387
jstring jniCreateString(JNIEnv* env, const jchar* unicodeChars, jsize len) {
321388
return (*env)->NewString(env, unicodeChars, len);
322389
}

include/nativehelper/JNIHelp.h

+11-2
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,14 @@ int jniThrowNullPointerException(C_JNIEnv* env, const char* msg);
8888
int jniThrowRuntimeException(C_JNIEnv* env, const char* msg);
8989

9090
/*
91-
* Throw a java.io.IOException, generating the message from errno.
91+
* Throw a java.io.IOException, generating the message from the given errno value.
9292
*/
93-
int jniThrowIOException(C_JNIEnv* env, int errnum);
93+
int jniThrowIOException(C_JNIEnv* env, int errno_value);
94+
95+
/*
96+
* Throw an android.system.ErrnoException, with the given function name and errno value.
97+
*/
98+
int jniThrowErrnoException(C_JNIEnv* env, const char* functionName, int errno_value);
9499

95100
/*
96101
* Returns a Java String object created from UTF-16 data either from jchar or,
@@ -143,6 +148,10 @@ inline int jniThrowIOException(JNIEnv* env, int errnum) {
143148
return jniThrowIOException(&env->functions, errnum);
144149
}
145150

151+
inline int jniThrowErrnoException(JNIEnv* env, const char* functionName, int errnum) {
152+
return jniThrowErrnoException(&env->functions, functionName, errnum);
153+
}
154+
146155
inline jstring jniCreateString(JNIEnv* env, const jchar* unicodeChars, jsize len) {
147156
return jniCreateString(&env->functions, unicodeChars, len);
148157
}

libnativehelper.map.txt

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ LIBNATIVEHELPER_S { # introduced=S
1717
jniThrowNullPointerException; # apex
1818
jniThrowRuntimeException; # apex
1919
jniThrowIOException; # apex
20+
jniThrowErrnoException; # apex
2021
jniCreateFileDescriptor; # apex
2122
jniGetFDFromFileDescriptor; # apex
2223
jniSetFileDescriptorOfFD; # apex

libnativehelper_lazy.c

+8-1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ enum MethodIndex {
5151
k_jniCreateString,
5252
k_jniLogException,
5353
k_jniRegisterNativeMethods,
54+
k_jniThrowErrnoException,
5455
k_jniThrowException,
5556
k_jniThrowExceptionFmt,
5657
k_jniThrowIOException,
@@ -132,6 +133,7 @@ static void InitializeOnce() {
132133
BIND_SYMBOL(jniCreateString);
133134
BIND_SYMBOL(jniLogException);
134135
BIND_SYMBOL(jniRegisterNativeMethods);
136+
BIND_SYMBOL(jniThrowErrnoException);
135137
BIND_SYMBOL(jniThrowException);
136138
BIND_SYMBOL(jniThrowExceptionFmt);
137139
BIND_SYMBOL(jniThrowIOException);
@@ -266,6 +268,11 @@ int jniRegisterNativeMethods(JNIEnv* env,
266268
INVOKE_METHOD(jniRegisterNativeMethods, M, env, className, gMethods, numMethods);
267269
}
268270

271+
int jniThrowErrnoException(JNIEnv* env, const char* functionName, int errnum) {
272+
typedef int (*M)(JNIEnv*, const char*, int);
273+
INVOKE_METHOD(jniThrowErrnoException, M, env, functionName, errnum);
274+
}
275+
269276
int jniThrowException(JNIEnv* env, const char* className, const char* msg) {
270277
typedef int (*M)(JNIEnv*, const char*, const char*);
271278
INVOKE_METHOD(jniThrowException, M, env, className, msg);
@@ -359,4 +366,4 @@ bool JniInvocationInit(struct JniInvocationImpl* instance, const char* library)
359366
const char* JniInvocationGetLibrary(const char* library, char* buffer) {
360367
typedef const char* (*M)(const char*, char*);
361368
INVOKE_METHOD(JniInvocationGetLibrary, M, library, buffer);
362-
}
369+
}

tests/libnativehelper_lazy_test.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ TEST_F(LibnativehelperLazyTest, NoLibnativehelperIsForJNIHelp) {
4747
EXPECT_DEATH(jniThrowIOException(env, 1), kLoadFailed);
4848
EXPECT_DEATH(jniThrowNullPointerException(env, "msg"), kLoadFailed);
4949
EXPECT_DEATH(jniThrowRuntimeException(env, "msg"), kLoadFailed);
50+
EXPECT_DEATH(jniThrowErrnoException(env, "fn", 1), kLoadFailed);
5051
}
5152

5253
TEST_F(LibnativehelperLazyTest, NoLibnativehelperIsForJNIPlatformHelp) {

tests_mts/jni/jni_helper_jni.cpp

+8
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ static void throwIOException(JNIEnv* env, jclass /*clazz*/, jint cause) {
8080
jniThrowIOException(env, cause);
8181
}
8282

83+
static void throwErrnoException(JNIEnv* env, jclass /*clazz*/, jstring functionName, jint cause) {
84+
ScopedUtfChars m(env, functionName);
85+
jniThrowErrnoException(env, m.c_str(), cause);
86+
}
87+
8388
static void logException(JNIEnv* env, jclass /*clazz*/, jthrowable throwable) {
8489
jniLogException(env, ANDROID_LOG_VERBOSE, __FILE__, throwable);
8590
}
@@ -197,6 +202,9 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
197202
MAKE_JNI_NATIVE_METHOD("throwIOException",
198203
"(I)V",
199204
throwIOException),
205+
MAKE_JNI_NATIVE_METHOD("throwErrnoException",
206+
"(Ljava/lang/String;I)V",
207+
throwErrnoException),
200208
MAKE_JNI_NATIVE_METHOD("logException",
201209
"(Ljava/lang/Throwable;)V",
202210
logException),

tests_mts/src/com/android/art/libnativehelper/JniHelpTest.java

+17
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
import java.nio.FloatBuffer;
3030
import java.nio.DoubleBuffer;
3131

32+
import android.system.ErrnoException;
33+
3234
import org.junit.Assert;
3335

3436
public class JniHelpTest extends AndroidTestCase {
@@ -39,6 +41,7 @@ private static native void throwExceptionWithIntFormat(String className,
3941
private static native void throwNullPointerException(String message);
4042
private static native void throwRuntimeException(String message);
4143
private static native void throwIOException(int cause) throws IOException;
44+
private static native void throwErrnoException(String fileName, int cause) throws ErrnoException;
4245
private static native void logException(Throwable throwable);
4346

4447
private static native FileDescriptor fileDescriptorCreate(int unixFd);
@@ -118,6 +121,20 @@ public void testIOException() {
118121
assertFalse(s1.equals(s2));
119122
}
120123

124+
public void testErrnoException() {
125+
final String functionName = "execve";
126+
final int err = 42;
127+
try {
128+
throwErrnoException(functionName, err);
129+
fail("Unreachable");
130+
} catch (ErrnoException e) {
131+
// The message contains the function name as well as the string for the errno, just only
132+
// check the first part of the message
133+
assertTrue("Function name", e.getMessage().startsWith(functionName));
134+
assertEquals(err, e.errno);
135+
}
136+
}
137+
121138
public void testLogException() {
122139
try {
123140
throw new RuntimeException("Exception for logging test");

0 commit comments

Comments
 (0)