How to fix an Android crash inside JNI code after onActivityResult?

Issue

I’ve published my app on Google Play Store and I’m facing one crash that affects many of my app users while I’m not able to reproduce it on my own Android devices.

The crash happens after the app presents the Google Play sign-in activity: when it returns to the app main activity through onActivityResult, a JNI function is called and there’s the crash.

The JNI code basically defines a C function pointer as the callback of the sign-in activity, which is communicated to the Java code through GetStaticMethodID (see code below).

I should be able to eradicate this flaw by simply removing the Google Play sign-in, but I’d like to understand why my code crashes on some Android configurations before taking this kind of decision.

At some point, here is how the C code asks for sign-in:

/*
    void *delegate;
    void *(*onSuccess)(void *);
    void *(*onError)(void *);
*/

    jclass class  (*env)->FindClass(env, "com/xxx/yyy/zzz");
    jmethodID method  (*env)->GetStaticMethodID(env, class, "signIn", "(JJJ)V");
    if (method)
        (*env)->CallStaticVoidMethod(env, class, method, delegate, onSuccess, onError);

Here is the implementation of signIn method, Java side:

    private final static int EXPLICIT_SIGN_IN  9001;

    private static long explicitSignInDelegate  0;
    private static long explicitSignInOnSuccess  0;
    private static long explicitSignInOnError  0;

    public static void signIn(long delegate, long onSuccess, long onError) {
        GoogleSignInOptions options   new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN)
                        .requestServerAuthCode(BuildConfig.SERVER_AUTH_CLIENT_ID)
                        .build();

        GoogleSignInClient signInClient  GoogleSignIn.getClient(activity,
                        options);

        signInClient.silentSignIn().addOnCompleteListener(activity,
                        new OnCompleteListener<GoogleSignInAccount>() {
                            @Override
                            public void onComplete(@NonNull Task<GoogleSignInAccount> task) {
                                if (task.isSuccessful()) {
                                    Callback(delegate, onSuccess);
                                }
                                else {
                                    explicitSignInDelegate  delegate;
                                    explicitSignInOnSuccess  onSuccess;
                                    explicitSignInOnError  onError;

                                    activity.startActivityForResult(signInClient.getSignInIntent(), EXPLICIT_SIGN_IN);
                                }
                            }
                        });
    }

Here is how onActivityResult is handled:

   public static void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
            case EXPLICIT_SIGN_IN: {
                GoogleSignInResult result  Auth.GoogleSignInApi.getSignInResultFromIntent(data);

                if ((result ! null) && result.isSuccess()) {
                    Callback(explicitSignInDelegate, explicitSignInOnSuccess);
                } else {
                    Callback(explicitSignInDelegate, explicitSignInOnError);
                }
            }
            break;

        }
    }

The Callback method is declared native and defined in JNI code as here:

JNIEXPORT void JNICALL Java_com_xxx_yyy_zzz_Callback( JNIEnv* env, jobject this, jlong delegate, jlong callback)
{
    if (callback)
    {
        void *(*function)(void *)  (void *(*)(void *))callback;
        function((void *)delegate);
    }
}

Note that pointers and function pointers are passed as ‘long’ when it comes to Java.

Currently, here is the kind of crashlog I get from the Google Play dashboard:

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
pid: 0, tid: 0 >>> com.xxx.yyy <<<

backtrace:
  #00  pc 00000000000f5420  [anon:libc_malloc:89080000]
  #01  pc 000000000004568f  /data/app/com.xxx.yyy-9vGJILyZlpOQinnnYi0z8g/lib/arm/libyyy.so (Java_com_xxx_yyy_zzz_Callback+26)
  #02  pc 00000000000f1901  /data/app/com.xxx.yyy-9vGJILyZlpOQinnnYi0z8g/oat/arm/base.odex

Solution

After being able to reproduce the crash on a dusty device, I understood that the problem was caused by the ‘long’ cast needed to pass pointer through JNI: on 32-bit CPU, pointers (void *) are the same size as ‘int’ so casting to ‘long’ should be done explicitly.

From C code:

/*
    void *delegate;
    void *(*onSuccess)(void *);
    void *(*onError)(void *);
*/

    jlong jdelegate  (jlong)delegate;
    jlong jonSuccess  (jlong)onSuccess;
    jlong jonError  (jlong)onError;

    jclass class  (*env)->FindClass(env, "com/xxx/yyy/zzz");
    jmethodID method  (*env)->GetStaticMethodID(env, class, "signIn", "(JJJ)V");
    if (method)
        (*env)->CallStaticVoidMethod(env, class, method, jdelegate, jonSuccess, jonError);

Answered By – ClĂ©ment Choquereau

Leave a Comment