Skip to content

Commit 28f9e90

Browse files
smilesa-maurice
authored andcommitted
Fixed race between Auth destructor and state change callbacks on Android.
PiperOrigin-RevId: 246533061
1 parent 1a64ce8 commit 28f9e90

File tree

4 files changed

+87
-18
lines changed

4 files changed

+87
-18
lines changed

auth/src/android/auth_android.cc

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17+
#include <assert.h>
1718
#include <jni.h>
1819

1920
#include "app/src/assert.h"
@@ -86,7 +87,8 @@ METHOD_LOOKUP_DEFINITION(signinmethodquery,
8687

8788
// clang-format off
8889
#define JNI_LISTENER_CALLBACK_METHODS(X) \
89-
X(Constructor, "<init>", "(J)V")
90+
X(Constructor, "<init>", "(J)V"), \
91+
X(Disconnect, "disconnect", "()V")
9092
// clang-format on
9193
METHOD_LOOKUP_DECLARATION(jnilistener, JNI_LISTENER_CALLBACK_METHODS)
9294
METHOD_LOOKUP_DEFINITION(
@@ -95,7 +97,8 @@ METHOD_LOOKUP_DEFINITION(
9597

9698
// clang-format off
9799
#define JNI_ID_TOKEN_LISTENER_CALLBACK_METHODS(X) \
98-
X(Constructor, "<init>", "(J)V")
100+
X(Constructor, "<init>", "(J)V"), \
101+
X(Disconnect, "disconnect", "()V")
99102
// clang-format on
100103
METHOD_LOOKUP_DECLARATION(jni_id_token_listener,
101104
JNI_ID_TOKEN_LISTENER_CALLBACK_METHODS)
@@ -217,6 +220,7 @@ void* CreatePlatformAuth(App* app, void* app_impl) {
217220
// Create the FirebaseAuth class in Java.
218221
jobject j_auth_impl = env->CallStaticObjectMethod(
219222
auth::GetClass(), auth::GetMethodId(auth::kGetInstance), j_app);
223+
assert(env->ExceptionCheck() == false);
220224

221225
// Ensure the reference hangs around.
222226
void* auth_impl = nullptr;
@@ -237,6 +241,7 @@ void Auth::InitPlatformAuth(AuthData* auth_data) {
237241
env->CallVoidMethod(AuthImpl(auth_data),
238242
auth::GetMethodId(auth::kAddAuthStateListener),
239243
j_listener);
244+
assert(env->ExceptionCheck() == false);
240245
// Convert listener from local to global ref, so it stays around.
241246
SetImplFromLocalRef(env, j_listener, &auth_data->listener_impl);
242247

@@ -250,6 +255,7 @@ void Auth::InitPlatformAuth(AuthData* auth_data) {
250255
env->CallVoidMethod(AuthImpl(auth_data),
251256
auth::GetMethodId(auth::kAddIdTokenListener),
252257
j_id_token_listener);
258+
assert(env->ExceptionCheck() == false);
253259
// Convert listener from local to global ref, so it stays around.
254260
SetImplFromLocalRef(env, j_id_token_listener,
255261
&auth_data->id_token_listener_impl);
@@ -266,12 +272,21 @@ void Auth::DestroyPlatformAuth(AuthData* auth_data) {
266272

267273
// Unregister the JniAuthStateListener and IdTokenListener from the
268274
// FirebaseAuth class.
275+
env->CallVoidMethod(static_cast<jobject>(auth_data->listener_impl),
276+
jnilistener::GetMethodId(jnilistener::kDisconnect));
277+
assert(env->ExceptionCheck() == false);
269278
env->CallVoidMethod(AuthImpl(auth_data),
270279
auth::GetMethodId(auth::kRemoveAuthStateListener),
271280
static_cast<jobject>(auth_data->listener_impl));
281+
assert(env->ExceptionCheck() == false);
282+
env->CallVoidMethod(static_cast<jobject>(auth_data->id_token_listener_impl),
283+
jni_id_token_listener::GetMethodId(
284+
jni_id_token_listener::kDisconnect));
285+
assert(env->ExceptionCheck() == false);
272286
env->CallVoidMethod(AuthImpl(auth_data),
273287
auth::GetMethodId(auth::kRemoveIdTokenListener),
274288
static_cast<jobject>(auth_data->id_token_listener_impl));
289+
assert(env->ExceptionCheck() == false);
275290

276291
// Deleting our global references should trigger the FirebaseAuth class and
277292
// FirebaseUser Java objects to be deleted.
@@ -327,12 +342,14 @@ static void ReadProviderResult(
327342
if (list != nullptr) {
328343
const int num_providers =
329344
env->CallIntMethod(list, util::list::GetMethodId(util::list::kSize));
345+
assert(env->ExceptionCheck() == false);
330346
data->providers.resize(num_providers);
331347

332348
for (int i = 0; i < num_providers; ++i) {
333349
// provider local reference is released in JniStringToString().
334350
jstring provider = static_cast<jstring>(env->CallObjectMethod(
335351
list, util::list::GetMethodId(util::list::kGet), i));
352+
assert(env->ExceptionCheck() == false);
336353
data->providers[i] = JniStringToString(env, provider);
337354
}
338355
env->DeleteLocalRef(list);

auth/src/data.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,14 @@ struct AuthData {
8787
// Reset the listeners so that they don't try to unregister themselves
8888
// when they are destroyed.
8989
ClearListeners();
90+
91+
app = nullptr;
92+
auth = nullptr;
93+
current_user = nullptr;
94+
auth_impl = nullptr;
95+
user_impl = nullptr;
96+
listener_impl = nullptr;
97+
id_token_listener_impl = nullptr;
9098
}
9199

92100
void ClearListeners() {

auth/src_java/com/google/firebase/auth/internal/cpp/JniAuthStateListener.java

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,29 +22,51 @@
2222
* Implements AuthStateListener by redirecting calls into C++.
2323
*/
2424
public class JniAuthStateListener implements FirebaseAuth.AuthStateListener {
25+
26+
/**
27+
* Lock that controls access to authData.
28+
*/
29+
private final Object lock = new Object();
30+
31+
/**
32+
* Pointer to an AuthData structure.
33+
*/
34+
private long cppAuthData;
35+
2536
/**
2637
* Constructor is called via JNI from C++.
27-
* The `long` value is actually a pointer to the data we pass back to
28-
* the caller in onAuthStateChanged(). It provides the caller with context.
38+
* cppAuthData is a pointer to the AuthData structure which is passed back to the caller in
39+
* onAuthStateChanged(). It provides the caller with context.
40+
*/
41+
public JniAuthStateListener(long cppAuthData) {
42+
this.cppAuthData = cppAuthData;
43+
}
44+
45+
/**
46+
* Remove the reference to the C++ AuthData object
2947
*/
30-
public JniAuthStateListener(long callbackData) {
31-
this.callbackData = callbackData;
48+
public void disconnect() {
49+
synchronized (lock) {
50+
cppAuthData = 0;
51+
}
3252
}
3353

3454
@Override
3555
public void onAuthStateChanged(FirebaseAuth auth) {
3656
AuthCommon.safeRunNativeMethod(new Runnable() {
3757
@Override
3858
public void run() {
39-
nativeOnAuthStateChanged(callbackData);
59+
synchronized (lock) {
60+
if (cppAuthData != 0) {
61+
nativeOnAuthStateChanged(cppAuthData);
62+
}
63+
}
4064
}
4165
});
4266
}
4367

4468
/**
4569
* This function is implemented in the Auth C++ library (auth_android.cc).
4670
*/
47-
private native void nativeOnAuthStateChanged(long callbackData);
48-
49-
private long callbackData;
71+
private native void nativeOnAuthStateChanged(long cppAuthData);
5072
}

auth/src_java/com/google/firebase/auth/internal/cpp/JniIdTokenListener.java

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,29 +22,51 @@
2222
* Implements IdTokenListener by redirecting calls into C++.
2323
*/
2424
public class JniIdTokenListener implements FirebaseAuth.IdTokenListener {
25+
26+
/**
27+
* Lock that controls access to authData.
28+
*/
29+
private final Object lock = new Object();
30+
31+
/**
32+
* Pointer to an AuthData structure.
33+
*/
34+
private long cppAuthData;
35+
2536
/**
2637
* Constructor is called via JNI from C++.
27-
* The `long` value is actually a pointer to the data we pass back to
28-
* the caller in onIdTokenChanged(). It provides the caller with context.
38+
* cppAuthData is a pointer to the AuthData structure which is passed back to the caller in
39+
* onAuthStateChanged(). It provides the caller with context.
40+
*/
41+
public JniIdTokenListener(long cppAuthData) {
42+
this.cppAuthData = cppAuthData;
43+
}
44+
45+
/**
46+
* Remove the reference to the C++ AuthData object
2947
*/
30-
public JniIdTokenListener(long callbackData) {
31-
this.callbackData = callbackData;
48+
public void disconnect() {
49+
synchronized (lock) {
50+
cppAuthData = 0;
51+
}
3252
}
3353

3454
@Override
3555
public void onIdTokenChanged(FirebaseAuth auth) {
3656
AuthCommon.safeRunNativeMethod(new Runnable() {
3757
@Override
3858
public void run() {
39-
nativeOnIdTokenChanged(callbackData);
59+
synchronized (lock) {
60+
if (cppAuthData != 0) {
61+
nativeOnIdTokenChanged(cppAuthData);
62+
}
63+
}
4064
}
4165
});
4266
}
4367

4468
/**
4569
* This function is implemented in the Auth C++ library (auth_android.cc).
4670
*/
47-
private native void nativeOnIdTokenChanged(long callbackData);
48-
49-
private long callbackData;
71+
private native void nativeOnIdTokenChanged(long cppAuthData);
5072
}

0 commit comments

Comments
 (0)