Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 53 additions & 8 deletions app/src/include/firebase/internal/future_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -152,38 +152,83 @@ class CompletionCallbackHandle {
void (*user_data_delete_fn_)(void*);
};

template <typename T>
struct TypedCompletionCallbackData {
typename Future<T>::TypedCompletionCallback callback;
void* user_data;
};

template <typename T>
inline void TypedCompletionCallbackTrampoline(const FutureBase& future,
void* data_ptr) {
auto* data = static_cast<TypedCompletionCallbackData<T>*>(data_ptr);
if (data != nullptr && data->callback != nullptr) {
data->callback(static_cast<const Future<T>&>(future), data->user_data);
}
}

template <typename T>
inline void DeleteTypedCompletionCallbackData(void* data_ptr) {
delete static_cast<TypedCompletionCallbackData<T>*>(data_ptr);
}

} // namespace detail

template <class T>
void Future<T>::OnCompletion(TypedCompletionCallback callback,
void* user_data) const {
FutureBase::OnCompletion(reinterpret_cast<CompletionCallback>(callback),
user_data);
MutexLock lock(mutex_);
if (api_ != nullptr) {
if (callback == nullptr) {
api_->AddCompletionCallback(handle_, nullptr, nullptr, nullptr,
/*clear_existing_callbacks=*/true);
Comment on lines +182 to +184

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

If callback is nullptr and the future is already completed, passing nullptr to api_->AddCompletionCallback will cause a crash (null pointer dereference) in ReferenceCountedFutureImpl::RunCallback when it attempts to execute the callback. To prevent this, pass a dummy callback that does nothing instead of nullptr.

Suggested change
if (callback == nullptr) {
api_->AddCompletionCallback(handle_, nullptr, nullptr, nullptr,
/*clear_existing_callbacks=*/true);
if (callback == nullptr) {
api_->AddCompletionCallback(handle_, [](const FutureBase&, void*){}, nullptr, nullptr,
/*clear_existing_callbacks=*/true);

} else {
auto* data =
new detail::TypedCompletionCallbackData<T>{callback, user_data};
api_->AddCompletionCallback(
handle_, detail::TypedCompletionCallbackTrampoline<T>, data,
detail::DeleteTypedCompletionCallbackData<T>,
/*clear_existing_callbacks=*/true);
}
}
}

#if defined(FIREBASE_USE_STD_FUNCTION)
template <class ResultType>
inline void Future<ResultType>::OnCompletion(
std::function<void(const Future<ResultType>&)> callback) const {
FutureBase::OnCompletion(
*reinterpret_cast<std::function<void(const FutureBase&)>*>(&callback));
FutureBase::OnCompletion([callback](const FutureBase& future) {
callback(static_cast<const Future<ResultType>&>(future));
});
Comment on lines +200 to +202

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If callback is an empty std::function (i.e., !callback), executing it inside the lambda will throw std::bad_function_call or cause an immediate crash if exceptions are disabled. To prevent this, check if the callback is valid. If it is empty, safely clear the existing callback using a dummy callback.

  if (!callback) {
    FutureBase::OnCompletion([](const FutureBase&, void*){}, nullptr);
  } else {
    FutureBase::OnCompletion([callback](const FutureBase& future) {
      callback(static_cast<const Future<ResultType>&>(future));
    });
  }

}
#endif // defined(FIREBASE_USE_STD_FUNCTION)

#if defined(INTERNAL_EXPERIMENTAL)
template <class T>
FutureBase::CompletionCallbackHandle Future<T>::AddOnCompletion(
TypedCompletionCallback callback, void* user_data) const {
return FutureBase::AddOnCompletion(
reinterpret_cast<CompletionCallback>(callback), user_data);
MutexLock lock(mutex_);
if (api_ != nullptr) {
if (callback == nullptr) {
return CompletionCallbackHandle();
}
auto* data =
new detail::TypedCompletionCallbackData<T>{callback, user_data};
return api_->AddCompletionCallback(
handle_, detail::TypedCompletionCallbackTrampoline<T>, data,
detail::DeleteTypedCompletionCallbackData<T>,
/*clear_existing_callbacks=*/false);
}
return CompletionCallbackHandle();
}

#if defined(FIREBASE_USE_STD_FUNCTION)
template <class ResultType>
inline FutureBase::CompletionCallbackHandle Future<ResultType>::AddOnCompletion(
std::function<void(const Future<ResultType>&)> callback) const {
return FutureBase::AddOnCompletion(
*reinterpret_cast<std::function<void(const FutureBase&)>*>(&callback));
return FutureBase::AddOnCompletion([callback](const FutureBase& future) {
callback(static_cast<const Future<ResultType>&>(future));
});
Comment on lines +229 to +231

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If callback is an empty std::function (i.e., !callback), executing it inside the lambda will throw std::bad_function_call or cause an immediate crash if exceptions are disabled. To prevent this, check if the callback is valid and return an empty CompletionCallbackHandle if it is empty.

  if (!callback) {
    return CompletionCallbackHandle();
  }
  return FutureBase::AddOnCompletion([callback](const FutureBase& future) {
    callback(static_cast<const Future<ResultType>&>(future));
  });

}
#endif // defined(FIREBASE_USE_STD_FUNCTION)

Expand Down
Loading