diff --git a/Android/firebase_dependencies.gradle b/Android/firebase_dependencies.gradle index 57cf7152a7..d1a2ecde24 100644 --- a/Android/firebase_dependencies.gradle +++ b/Android/firebase_dependencies.gradle @@ -20,8 +20,8 @@ def firebaseDependenciesMap = [ 'admob' : ['com.google.firebase:firebase-ads:18.2.0', 'com.google.android.gms:play-services-measurement-sdk-api:17.2.0'], 'analytics' : ['com.google.firebase:firebase-analytics:17.2.0'], - 'auth' : ['com.google.firebase:firebase-auth:19.0.0'], - 'database' : ['com.google.firebase:firebase-database:19.1.0'], + 'auth' : ['com.google.firebase:firebase-auth:19.1.0'], + 'database' : ['com.google.firebase:firebase-database:19.2.0'], 'dynamic_links' : ['com.google.firebase:firebase-dynamic-links:19.0.0'], 'functions' : ['com.google.firebase:firebase-functions:19.0.1'], 'instance_id' : ['com.google.firebase:firebase-iid:20.0.0'], @@ -29,9 +29,9 @@ def firebaseDependenciesMap = [ // Messaging has an additional local dependency to include. 'messaging' : ['com.google.firebase:firebase-messaging:20.0.0', 'firebase_cpp_sdk.messaging:messaging_java'], - 'performance' : ['com.google.firebase:firebase-perf:19.0.0'], - 'remote_config' : ['com.google.firebase:firebase-config:19.0.1'], - 'storage' : ['com.google.firebase:firebase-storage:19.0.1'] + 'performance' : ['com.google.firebase:firebase-perf:19.0.1'], + 'remote_config' : ['com.google.firebase:firebase-config:19.0.3'], + 'storage' : ['com.google.firebase:firebase-storage:19.1.0'] ] // A map of library to the gradle resources that they depend upon. diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c819fa430..6708d35ab8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,12 @@ option(FIREBASE_CPP_BUILD_TESTS option(FIREBASE_FORCE_FAKE_SECURE_STORAGE "Disable use of platform secret store and use fake impl." OFF) +if(WIN32) + # Turn on the use of the __cplusplus compiler define that is used to detect if + # move operators are supported + add_definitions("/Zc:__cplusplus") +endif() + list(INSERT CMAKE_MODULE_PATH 0 ${CMAKE_CURRENT_LIST_DIR}/cmake) include(external_rules) diff --git a/README.md b/README.md index f4a20515b4..0b509a8111 100644 --- a/README.md +++ b/README.md @@ -49,20 +49,61 @@ git clone https://github.com/firebase/firebase-cpp-sdk.git ## Prerequisites The following prerequisites are required for all platforms. Be sure to add any directories to your PATH as needed. + - [CMake](https://cmake.org/), version 3.1, or newer -- [Python](https://www.python.com/), the latest version of 2.7, or newer +- [Python2](https://www.python.com/), version of 2.7, or newer - [Abseil-py](https://github.com/abseil/abseil-py) +Note: Once python is installed you can use the following commands to install +required packages: + +* python -m ensurepip --default-pip +* python -m pip install --user absl-py +* python -m pip install --user protobuf + ### Prerequisites for Desktop The following prerequisites are required when building the libraries for desktop platforms. + - [OpenSSL](https://www.openssl.org/), needed for Realtime Database - [Protobuf](https://github.com/protocolbuffers/protobuf/blob/master/src/README.md), needed for Remote Config +### Prerequisites for Windows +Prebuilt packages for openssl can be found using google and if CMake fails to +find the install path use the command line option +**-DOPENSSL_ROOT_DIR=[Open SSL Dir]**. + +Since there are no prebuilt packages for protobuf, getting it working on Windows +is a little tricky. The following steps can be used as a guide: + +* Download source [zip from github](https://github.com/protocolbuffers/protobuf/releases/download/v3.9.2/protobuf-all-3.9.2.zip). +* Extract source and open command prompt to root folder +* Make new folder **vsprojects** +* CD to **vsprojects** +* run cmake: **cmake ..\cmake -Dprotobuf_BUILD_TESTS=OFF -A Win32** +* Build solution +* Add command line define to firebase cmake command (see below) + **-DPROTOBUF_SRC_ROOT_FOLDER=[Source Root Folder]** + +Note: For x64 builds folder needs to be **vsprojects\x64** and change **Win32** +in cmake command to **x64** + +### Prerequisites for Mac +Home brew can be used to install required dependencies: + +```bash +# https://github.com/protocolbuffers/protobuf/blob/master/kokoro/macos/prepare_build_macos_rc#L20 +ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" +source $HOME/.rvm/scripts/rvm +brew install cmake protobuf python2 +sudo chown -R $(whoami) /usr/local +``` + ### Prerequisites for Android The following prerequisites are required when building the libraries for Android. + - Android SDK, Android NDK, and CMake for Android (version 3.10.2 recommended) - Download sdkmanager (either independently, or as a part of Android Studio) [here](https://developer.android.com/studio/#downloads) diff --git a/admob/CMakeLists.txt b/admob/CMakeLists.txt index 06c19e58cf..d070679f07 100644 --- a/admob/CMakeLists.txt +++ b/admob/CMakeLists.txt @@ -17,7 +17,9 @@ cmake_minimum_required (VERSION 3.1) set (CMAKE_CXX_STANDARD 11) +include(binary_to_array) include(download_pod_headers) +include(firebase_cpp_gradle) project(firebase_admob NONE) enable_language(C) @@ -36,6 +38,8 @@ set(common_SRCS src/common/rewarded_video_internal.cc) # Define the resource build needed for Android +firebase_cpp_gradle(":admob:admob_resources:generateDexJarRelease" + "${CMAKE_CURRENT_LIST_DIR}/admob_resources/build/dexed.jar") binary_to_array("admob_resources" "${CMAKE_CURRENT_LIST_DIR}/admob_resources/build/dexed.jar" "firebase_admob" @@ -107,7 +111,9 @@ else() add_definitions(-include assert.h -include string.h) endif() -if(IOS) +if(ANDROID) + firebase_cpp_proguard_file(admob) +elseif(IOS) # AdMob for iOS uses weak references, which requires enabling Automatic # Reference Counting (ARC). set_property( @@ -119,7 +125,7 @@ if(IOS) set(pod_target_name "download_admob_pod_headers") set(pods_dir "${PROJECT_BINARY_DIR}/Pods") set(pod_list "") - list(APPEND pod_list "'Firebase/AdMob', '6.9.0'") + list(APPEND pod_list "'Firebase/AdMob', '6.10.0'") setup_pod_headers_target("${pod_target_name}" "${pods_dir}" "${pod_list}") @@ -129,6 +135,14 @@ if(IOS) PRIVATE ${base_header_dir}/Google-Mobile-Ads-SDK ) + string(CONCAT google_mobile_ads_framework_path + "${pods_dir}/Pods/Google-Mobile-Ads-SDK/" + "Frameworks/GoogleMobileAdsFramework-Current/GoogleMobileAds.framework") + # AdMob expects the header files to be in a subfolder, so set up a symlink to + # accomplish that. + symlink_framework_headers(firebase_admob ${pod_target_name} + ${google_mobile_ads_framework_path} GoogleMobileAds + ) # Add a dependency to downloading the headers onto admob. add_dependencies(firebase_admob ${pod_target_name}) diff --git a/admob/admob_resources/AndroidManifest.xml b/admob/admob_resources/AndroidManifest.xml new file mode 100644 index 0000000000..02feeb8dfa --- /dev/null +++ b/admob/admob_resources/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/admob/admob_resources/build.gradle b/admob/admob_resources/build.gradle index 930a9be5e4..a1fe4705d3 100644 --- a/admob/admob_resources/build.gradle +++ b/admob/admob_resources/build.gradle @@ -36,7 +36,7 @@ android { sourceSets { main { - manifest.srcFile '../../android_build_files/AndroidManifest.xml' + manifest.srcFile 'AndroidManifest.xml' java { srcDirs = ['../src_java/com/google/firebase/admob/internal/cpp'] } diff --git a/analytics/CMakeLists.txt b/analytics/CMakeLists.txt index 7580a19a64..dad33a3c77 100644 --- a/analytics/CMakeLists.txt +++ b/analytics/CMakeLists.txt @@ -18,6 +18,7 @@ cmake_minimum_required (VERSION 3.1) set (CMAKE_CXX_STANDARD 11) include(download_pod_headers) +include(firebase_cpp_gradle) project(firebase_analytics NONE) enable_language(C) @@ -121,7 +122,9 @@ else() add_definitions(-include assert.h -include string.h) endif() -if(IOS) +if(ANDROID) + firebase_cpp_proguard_file(analytics) +elseif(IOS) # Enable Automatic Reference Counting (ARC). set_property( TARGET firebase_analytics @@ -132,17 +135,19 @@ if(IOS) set(pod_target_name "download_analytics_pod_headers") set(pods_dir "${PROJECT_BINARY_DIR}/Pods") set(pod_list "") - list(APPEND pod_list "'Firebase/Core', '6.9.0'") - list(APPEND pod_list "'Firebase/Analytics', '6.9.0'") + list(APPEND pod_list "'Firebase/Core', '6.10.0'") + list(APPEND pod_list "'Firebase/Analytics', '6.10.0'") setup_pod_headers_target("${pod_target_name}" "${pods_dir}" "${pod_list}") # Add the Cocoapod headers to the include directories set(base_header_dir "${pods_dir}/Pods/Headers/Public") + set(analytics_framework_path + "${pods_dir}/Pods/FirebaseAnalytics/Frameworks/FirebaseAnalytics.framework") target_include_directories(firebase_analytics PRIVATE ${base_header_dir}/FirebaseCore - ${base_header_dir}/FirebaseAnalytics/FirebaseAnalytics + ${analytics_framework_path}/Headers ) # Add a dependency to downloading the headers onto analytics. diff --git a/analytics/src/analytics_android.cc b/analytics/src/analytics_android.cc index e024a15e68..22b89166a3 100644 --- a/analytics/src/analytics_android.cc +++ b/analytics/src/analytics_android.cc @@ -397,7 +397,7 @@ Future GetAnalyticsInstanceId() { util::RegisterCallbackOnTask( env, task, [](JNIEnv* env, jobject result, util::FutureResult result_code, - int status, const char* status_message, void* callback_data) { + const char* status_message, void* callback_data) { auto* future_data = internal::FutureData::Get(); if (future_data) { bool success = diff --git a/analytics/src/analytics_ios.mm b/analytics/src/analytics_ios.mm index e87c951b7a..1bfaab490f 100644 --- a/analytics/src/analytics_ios.mm +++ b/analytics/src/analytics_ios.mm @@ -24,19 +24,103 @@ #include "app/src/include/firebase/version.h" #include "app/src/assert.h" #include "app/src/log.h" +#include "app/src/mutex.h" +#include "app/src/time.h" +#include "app/src/thread.h" +#include "app/src/util.h" #include "app/src/util_ios.h" namespace firebase { namespace analytics { +// Used to workaround b/143656277 and b/110166640 +class AnalyticsDataResetter { + private: + enum ResetState { + kResetStateNone = 0, + kResetStateRequested, + kResetStateRetry, + }; + public: + // Initialize the class. + AnalyticsDataResetter() : reset_state_(kResetStateNone), reset_timestamp_(0) {} + + // Reset analytics data. + void Reset() { + MutexLock lock(mutex_); + reset_timestamp_ = firebase::internal::GetTimestampEpoch(); + reset_state_ = kResetStateRequested; + instance_id_ = util::StringFromNSString([FIRAnalytics appInstanceID]); + [FIRAnalytics resetAnalyticsData]; + } + + // Get the instance ID, returning a non-empty string if it's valid or an empty string if it's + // still being reset. + std::string GetInstanceId() { + MutexLock lock(mutex_); + std::string current_instance_id = util::StringFromNSString([FIRAnalytics appInstanceID]); + uint64_t reset_time_elapsed_milliseconds = GetResetTimeElapsedMilliseconds(); + switch (reset_state_) { + case kResetStateNone: + break; + case kResetStateRequested: + if (reset_time_elapsed_milliseconds >= kResetRetryIntervalMilliseconds) { + // Firebase Analytics on iOS can take a while to initialize, in this case we try to reset + // again if the instance ID hasn't changed for a while. + reset_state_ = kResetStateRetry; + reset_timestamp_ = firebase::internal::GetTimestampEpoch(); + [FIRAnalytics resetAnalyticsData]; + return std::string(); + } + FIREBASE_CASE_FALLTHROUGH; + + case kResetStateRetry: + if ((current_instance_id.empty() || current_instance_id == instance_id_) && + reset_time_elapsed_milliseconds < kResetTimeoutMilliseconds) { + return std::string(); + } + break; + } + instance_id_ = current_instance_id; + return current_instance_id; + } + + private: + // Get the time elapsed in milliseconds since reset was requested. + uint64_t GetResetTimeElapsedMilliseconds() const { + return firebase::internal::GetTimestampEpoch() - reset_timestamp_; + } + + private: + Mutex mutex_; + // Reset attempt. + ResetState reset_state_; + // When a reset was last requested. + uint64_t reset_timestamp_; + // Instance ID before it was reset. + std::string instance_id_; + + // Time to wait before trying to reset again. + static const uint64_t kResetRetryIntervalMilliseconds; + // Time to wait before giving up on resetting the ID. + static const uint64_t kResetTimeoutMilliseconds; +}; + DEFINE_FIREBASE_VERSION_STRING(FirebaseAnalytics); +const uint64_t AnalyticsDataResetter::kResetRetryIntervalMilliseconds = 1000; +const uint64_t AnalyticsDataResetter::kResetTimeoutMilliseconds = 5000; + static const double kMillisecondsPerSecond = 1000.0; +static Mutex g_mutex; // NOLINT static bool g_initialized = false; +static AnalyticsDataResetter *g_resetter = nullptr; // Initialize the API. void Initialize(const ::firebase::App& app) { + MutexLock lock(g_mutex); g_initialized = true; + g_resetter = new AnalyticsDataResetter(); internal::RegisterTerminateOnDefaultAppDestroy(); internal::FutureData::Create(); } @@ -50,8 +134,11 @@ void Initialize(const ::firebase::App& app) { // Terminate the API. void Terminate() { + MutexLock lock(g_mutex); internal::FutureData::Destroy(); internal::UnregisterTerminateOnDefaultAppDestroy(); + delete g_resetter; + g_resetter = nullptr; g_initialized = false; } @@ -169,22 +256,40 @@ void SetCurrentScreen(const char* screen_name, const char* screen_class) { } void ResetAnalyticsData() { + MutexLock lock(g_mutex); FIREBASE_ASSERT_RETURN_VOID(internal::IsInitialized()); - [FIRAnalytics resetAnalyticsData]; + g_resetter->Reset(); } Future GetAnalyticsInstanceId() { + MutexLock lock(g_mutex); FIREBASE_ASSERT_RETURN(Future(), internal::IsInitialized()); auto* api = internal::FutureData::Get()->api(); const auto future_handle = api->SafeAlloc( internal::kAnalyticsFnGetAnalyticsInstanceId); - api->CompleteWithResult( - future_handle, 0, "", - util::StringFromNSString([FIRAnalytics appInstanceID])); + static int kPollTimeMs = 100; + Thread get_id_thread([](SafeFutureHandle* handle) { + for ( ; ; ) { + { + MutexLock lock(g_mutex); + if (!internal::IsInitialized()) break; + std::string instance_id = g_resetter->GetInstanceId(); + if (!instance_id.empty()) { + internal::FutureData::Get()->api()->CompleteWithResult( + *handle, 0, "", instance_id); + break; + } + } + firebase::internal::Sleep(kPollTimeMs); + } + delete handle; + }, new SafeFutureHandle(future_handle)); + get_id_thread.Detach(); return Future(api, future_handle.get()); } Future GetAnalyticsInstanceIdLastResult() { + MutexLock lock(g_mutex); FIREBASE_ASSERT_RETURN(Future(), internal::IsInitialized()); return static_cast&>( internal::FutureData::Get()->api()->LastResult( diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 453a49c48d..3cca895f7b 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -20,6 +20,7 @@ set (CMAKE_CXX_STANDARD 11) include(FindPkgConfig) include(binary_to_array) include(download_pod_headers) +include(firebase_cpp_gradle) project(firebase_app NONE) enable_language(C) @@ -32,14 +33,20 @@ binary_to_array("google_services_resource" "${FIREBASE_GEN_FILE_DIR}/app") # Define the resource builds needed for Android +firebase_cpp_gradle(":app:app_resources:generateDexJarRelease" + "${CMAKE_CURRENT_LIST_DIR}/app_resources/build/dexed.jar") binary_to_array("app_resources" "${CMAKE_CURRENT_LIST_DIR}/app_resources/build/dexed.jar" "firebase_app" "${FIREBASE_GEN_FILE_DIR}/app") +firebase_cpp_gradle(":app:google_api_resources:generateDexJarRelease" + "${CMAKE_CURRENT_LIST_DIR}/google_api_resources/build/dexed.jar") binary_to_array("google_api_resources" "${CMAKE_CURRENT_LIST_DIR}/google_api_resources/build/dexed.jar" "google_api" "${FIREBASE_GEN_FILE_DIR}/app") +firebase_cpp_gradle(":app:invites_resources:generateDexJarRelease" + "${CMAKE_CURRENT_LIST_DIR}/invites_resources/build/dexed.jar") binary_to_array("invites_resources" "${CMAKE_CURRENT_LIST_DIR}/invites_resources/build/dexed.jar" "firebase_invites" @@ -340,7 +347,9 @@ if(MSVC) add_definitions(-DNOMINMAX) endif() -if(IOS) +if(ANDROID) + firebase_cpp_proguard_file(app) +elseif(IOS) # Enable Automatic Reference Counting (ARC). set_property( TARGET firebase_app @@ -351,8 +360,8 @@ if(IOS) set(pod_target_name "download_app_pod_headers") set(pods_dir "${PROJECT_BINARY_DIR}/Pods") set(pod_list "") - list(APPEND pod_list "'Firebase/Core', '6.9.0'") - list(APPEND pod_list "'Firebase/DynamicLinks', '6.9.0'") + list(APPEND pod_list "'Firebase/Core', '6.10.0'") + list(APPEND pod_list "'Firebase/DynamicLinks', '6.10.0'") setup_pod_headers_target("${pod_target_name}" "${pods_dir}" "${pod_list}") diff --git a/app/rest/controller_curl.cc b/app/rest/controller_curl.cc index 1057ab598a..b61608a210 100644 --- a/app/rest/controller_curl.cc +++ b/app/rest/controller_curl.cc @@ -45,11 +45,11 @@ ControllerCurl::~ControllerCurl() { } #if defined(FIREBASE_USE_MOVE_OPERATORS) || defined(DOXYGEN) -ControllerCurl::ControllerCurl(ControllerCurl&& other) { +ControllerCurl::ControllerCurl(ControllerCurl&& other) noexcept { *this = std::move(other); } -ControllerCurl& ControllerCurl::operator=(ControllerCurl&& other) { +ControllerCurl& ControllerCurl::operator=(ControllerCurl&& other) noexcept { direction_ = other.direction_; transport_ = other.transport_; is_paused_ = other.is_paused_; diff --git a/app/rest/controller_curl.h b/app/rest/controller_curl.h index 53343be599..9edbdc4001 100644 --- a/app/rest/controller_curl.h +++ b/app/rest/controller_curl.h @@ -54,9 +54,9 @@ class ControllerCurl : public Controller { ~ControllerCurl() override; #if defined(FIREBASE_USE_MOVE_OPERATORS) || defined(DOXYGEN) - ControllerCurl(ControllerCurl&& other); + ControllerCurl(ControllerCurl&& other) noexcept; - ControllerCurl& operator=(ControllerCurl&& other); + ControllerCurl& operator=(ControllerCurl&& other) noexcept; #endif // defined(FIREBASE_USE_MOVE_OPERATORS) || defined(DOXYGEN) // Pauses whatever the handle is doing. diff --git a/app/src/app_android.cc b/app/src/app_android.cc index f4e63f8a66..8ffb1f3896 100644 --- a/app/src/app_android.cc +++ b/app/src/app_android.cc @@ -420,7 +420,7 @@ AppOptions* AppOptions::LoadDefault(AppOptions* app_options, return app_options; } -App::App() : activity_(nullptr), internal_(nullptr) {} +void App::Initialize() {} App::~App() { app_common::RemoveApp(this); diff --git a/app/src/app_common.cc b/app/src/app_common.cc index e1a2eae5b2..3c8a12d2d1 100644 --- a/app/src/app_common.cc +++ b/app/src/app_common.cc @@ -46,6 +46,17 @@ namespace FIREBASE_NAMESPACE { +#ifdef FIREBASE_LINUX_BUILD_CONFIG_STRING +void CheckCompilerString(const char* input) { + FIREBASE_ASSERT_MESSAGE( + strcmp(FIREBASE_LINUX_BUILD_CONFIG_STRING, input) == 0, + "The compiler or stdlib library Firebase was compiled with does not " + "match what is being used to compile this application." + " [Lib: '%s' != Bin: '%s']", + FIREBASE_LINUX_BUILD_CONFIG_STRING, input); +} +#endif // FIREBASE_LINUX_BUILD_CONFIG_STRING + // Default app name. const char* const kDefaultAppName = "__FIRAPP_DEFAULT"; diff --git a/app/src/app_desktop.cc b/app/src/app_desktop.cc index 2ac8e95126..eaa57873c8 100644 --- a/app/src/app_desktop.cc +++ b/app/src/app_desktop.cc @@ -117,7 +117,7 @@ AppOptions* AppOptions::LoadDefault(AppOptions* options) { return nullptr; } -App::App() { +void App::Initialize() { internal_ = new internal::AppInternal(); } diff --git a/app/src/app_ios.mm b/app/src/app_ios.mm index 32c7caac00..11e50d4332 100644 --- a/app/src/app_ios.mm +++ b/app/src/app_ios.mm @@ -220,7 +220,7 @@ static void PlatformOptionsToAppOptions(FIROptions* platform_options, return app_options; } -App::App() : internal_(nullptr) {} +void App::Initialize() {} App::~App() { app_common::RemoveApp(this); diff --git a/app/src/app_stub.cc b/app/src/app_stub.cc index 7275d52204..69347aa653 100644 --- a/app/src/app_stub.cc +++ b/app/src/app_stub.cc @@ -31,7 +31,8 @@ DEFINE_FIREBASE_VERSION_STRING(Firebase); const char* const kDefaultAppName = "default"; -App::App() : data_(new internal::FunctionRegistry) { +void App::Initialize() { + data_ = new internal::FunctionRegistry; LogDebug("Creating firebase::App for %s", kFirebaseVersionString); } diff --git a/app/src/include/firebase/app.h b/app/src/include/firebase/app.h index 2e9a63f2be..d8967515eb 100644 --- a/app/src/include/firebase/app.h +++ b/app/src/include/firebase/app.h @@ -39,6 +39,11 @@ namespace FIREBASE_NAMESPACE { +#ifdef FIREBASE_LINUX_BUILD_CONFIG_STRING +// Check to see if the shared object compiler string matches the input +void CheckCompilerString(const char* input); +#endif // FIREBASE_LINUX_BUILD_CONFIG_STRING + // Predeclarations. #ifdef INTERNAL_EXPERIMENTAL namespace internal { @@ -754,7 +759,21 @@ class App { private: /// Construct the object. - App(); + App() : +#if FIREBASE_PLATFORM_ANDROID || defined(DOXYGEN) + activity_(nullptr), +#endif + internal_(nullptr) { + Initialize(); + +#ifdef FIREBASE_LINUX_BUILD_CONFIG_STRING + CheckCompilerString(FIREBASE_LINUX_BUILD_CONFIG_STRING); +#endif // FIREBASE_LINUX_BUILD_CONFIG_STRING + } + + + /// Initialize internal implementation + void Initialize(); #ifndef SWIG // diff --git a/app/src/include/firebase/future.h b/app/src/include/firebase/future.h index 93ff9b5d9d..b4e8b2c92a 100644 --- a/app/src/include/firebase/future.h +++ b/app/src/include/firebase/future.h @@ -117,10 +117,10 @@ class FutureBase { /// Move constructor and operator. /// Move is more efficient than copy and delete because we don't touch the /// reference counting in the API. - FutureBase(FutureBase&& rhs); + FutureBase(FutureBase&& rhs) noexcept; /// Copy an untyped future. - FutureBase& operator=(FutureBase&& rhs); + FutureBase& operator=(FutureBase&& rhs) noexcept; #endif // defined(FIREBASE_USE_MOVE_OPERATORS) /// Explicitly release the internal resources for a future. @@ -188,13 +188,7 @@ class FutureBase { /// @param[in] callback Function pointer to your callback. /// @param[in] user_data Optional user data. We will pass this back to your /// callback. -#if defined(INTERNAL_EXPERIMENTAL) - /// @return A handle that can be passed to RemoveOnCompletion. - CompletionCallbackHandle -#else - void -#endif - OnCompletion(CompletionCallback callback, void* user_data) const; + void OnCompletion(CompletionCallback callback, void* user_data) const; #if defined(FIREBASE_USE_STD_FUNCTION) || defined(DOXYGEN) /// Register a single callback that will be called at most once, when the @@ -216,13 +210,7 @@ class FutureBase { /// /// @note This method is not available when using STLPort on Android, as /// `std::function` is not supported on STLPort. -#if defined(INTERNAL_EXPERIMENTAL) - /// @return A handle that can be passed to RemoveOnCompletion. - CompletionCallbackHandle -#else - void -#endif - OnCompletion(std::function callback) const; + void OnCompletion(std::function callback) const; #endif // defined(FIREBASE_USE_STD_FUNCTION) || defined(DOXYGEN) #if defined(INTERNAL_EXPERIMENTAL) @@ -267,10 +255,10 @@ class FutureBase { #endif // defined(FIREBASE_USE_STD_FUNCTION) || defined(DOXYGEN) /// Unregisters a callback that was previously registered with - /// AddOnCompletion or OnCompletion. + /// AddOnCompletion. /// /// @param[in] completion_handle The return value of a previous call to - /// AddOnCompletion or OnCompletion. + /// AddOnCompletion. void RemoveOnCompletion(CompletionCallbackHandle completion_handle) const; #endif // defined(INTERNAL_EXPERIMENTAL) @@ -421,12 +409,7 @@ class Future : public FutureBase { /// @note This is the same callback as FutureBase::OnCompletion(), so you /// can't expect to set both and have both run; again, only the most recently /// registered one will run. -#if defined(INTERNAL_EXPERIMENTAL) - /// @return A handle that can be passed to RemoveOnCompletion. - inline CompletionCallbackHandle -#else inline void -#endif OnCompletion(TypedCompletionCallback callback, void* user_data) const; #if defined(FIREBASE_USE_STD_FUNCTION) || defined(DOXYGEN) @@ -444,12 +427,7 @@ class Future : public FutureBase { /// @note This is the same callback as FutureBase::OnCompletion(), so you /// can't expect to set both and have both run; again, only the most recently /// registered one will run. -#if defined(INTERNAL_EXPERIMENTAL) - /// @return A handle that can be passed to RemoveOnCompletion. - inline CompletionCallbackHandle -#else inline void -#endif OnCompletion(std::function&)> callback) const; #endif // defined(FIREBASE_USE_STD_FUNCTION) || defined(DOXYGEN) diff --git a/app/src/include/firebase/internal/common.h b/app/src/include/firebase/internal/common.h index 01e11f4feb..b2952f2e78 100644 --- a/app/src/include/firebase/internal/common.h +++ b/app/src/include/firebase/internal/common.h @@ -23,10 +23,12 @@ #include // Move operators use rvalue references, which are a C++11 extension. +// Also, Visual Studio 2010 and later actually support move operators despite +// reporting __cplusplus to be 199711L, so explicitly check for that. // Also, stlport doesn't implement std::move(). -#if __cplusplus >= 201103L && !defined(_STLPORT_VERSION) +#if (__cplusplus >= 201103L || _MSC_VER >= 1600) && !defined(_STLPORT_VERSION) #define FIREBASE_USE_MOVE_OPERATORS -#endif // __cplusplus >= 201103L && !defined(_STLPORT_VERSION) +#endif // stlport doesn't implement std::function. #if !defined(_STLPORT_VERSION) diff --git a/app/src/include/firebase/internal/future_impl.h b/app/src/include/firebase/internal/future_impl.h index a16039d862..6ed34479ae 100644 --- a/app/src/include/firebase/internal/future_impl.h +++ b/app/src/include/firebase/internal/future_impl.h @@ -162,34 +162,15 @@ class CompletionCallbackHandle { } // namespace detail -#ifdef INTERNAL_EXPERIMENTAL template -FutureBase::CompletionCallbackHandle -Future::OnCompletion( - TypedCompletionCallback callback, void* user_data) const { - return FutureBase::OnCompletion( - reinterpret_cast(callback), user_data); -} -#else void -template Future::OnCompletion( TypedCompletionCallback callback, void* user_data) const { FutureBase::OnCompletion( reinterpret_cast(callback), user_data); } -#endif #if defined(FIREBASE_USE_STD_FUNCTION) -#if defined(INTERNAL_EXPERIMENTAL) -template -inline FutureBase::CompletionCallbackHandle -Future::OnCompletion( - std::function&)> callback) const { - return FutureBase::OnCompletion( - *reinterpret_cast*>(&callback)); -} -#else template inline void Future::OnCompletion( @@ -197,7 +178,6 @@ Future::OnCompletion( FutureBase::OnCompletion( *reinterpret_cast*>(&callback)); } -#endif #endif // defined(FIREBASE_USE_STD_FUNCTION) #if defined(INTERNAL_EXPERIMENTAL) @@ -251,7 +231,7 @@ inline FutureBase& FutureBase::operator=(const FutureBase& rhs) { } #if defined(FIREBASE_USE_MOVE_OPERATORS) -inline FutureBase::FutureBase(FutureBase&& rhs) +inline FutureBase::FutureBase(FutureBase&& rhs) noexcept : api_(NULL) // NOLINT { detail::UnregisterForCleanup(rhs.api_, &rhs); @@ -259,7 +239,7 @@ inline FutureBase::FutureBase(FutureBase&& rhs) detail::RegisterForCleanup(api_, this); } -inline FutureBase& FutureBase::operator=(FutureBase&& rhs) { +inline FutureBase& FutureBase::operator=(FutureBase&& rhs) noexcept { Release(); detail::UnregisterForCleanup(rhs.api_, &rhs); api_ = rhs.api_; @@ -296,16 +276,6 @@ inline const void* FutureBase::result_void() const { return api_ == NULL ? NULL : api_->GetFutureResult(handle_); // NOLINT } -#if defined(INTERNAL_EXPERIMENTAL) -inline FutureBase::CompletionCallbackHandle FutureBase::OnCompletion( - CompletionCallback callback, void* user_data) const { - if (api_ != NULL) { // NOLINT - return api_->AddCompletionCallback(handle_, callback, user_data, nullptr, - /*clear_existing_callbacks=*/ true); - } - return CompletionCallbackHandle(); -} -#else inline void FutureBase::OnCompletion(CompletionCallback callback, void* user_data) const { if (api_ != NULL) { // NOLINT @@ -313,7 +283,6 @@ inline void FutureBase::OnCompletion(CompletionCallback callback, /*clear_existing_callbacks=*/ true); } } -#endif #if defined(INTERNAL_EXPERIMENTAL) inline FutureBase::CompletionCallbackHandle @@ -335,16 +304,6 @@ inline void FutureBase::RemoveOnCompletion( #endif // defined(INTERNAL_EXPERIMENTAL) #if defined(FIREBASE_USE_STD_FUNCTION) -#if defined(INTERNAL_EXPERIMENTAL) -inline FutureBase::CompletionCallbackHandle FutureBase::OnCompletion( - std::function callback) const { - if (api_ != NULL) { // NOLINT - return api_->AddCompletionCallbackLambda(handle_, callback, - /*clear_existing_callbacks=*/ true); - } - return CompletionCallbackHandle(); -} -#else inline void FutureBase::OnCompletion( std::function callback) const { if (api_ != NULL) { // NOLINT @@ -352,7 +311,6 @@ inline void FutureBase::OnCompletion( /*clear_existing_callbacks=*/ true); } } -#endif #if defined(INTERNAL_EXPERIMENTAL) inline FutureBase::CompletionCallbackHandle FutureBase::AddOnCompletion( diff --git a/app/src/include/firebase/internal/platform.h b/app/src/include/firebase/internal/platform.h index 0d5c947442..e2518195ae 100644 --- a/app/src/include/firebase/internal/platform.h +++ b/app/src/include/firebase/internal/platform.h @@ -65,6 +65,37 @@ #define FIREBASE_PLATFORM_UNKNOWN 1 #endif +#if FIREBASE_PLATFORM_LINUX + +// Include std library header to get version defines +#include + +#if defined(__clang__) +#define FIREBASE_COMPILER_CLANG 1 +#elif defined(__GNUC__) +#define FIREBASE_COMPILER_GCC 1 +#endif + +#if defined(_LIBCPP_VERSION) +#define FIREBASE_STANDARD_LIBCPP 1 +#elif defined(__GLIBCXX__) +#define FIREBASE_STANDARD_LIBSTDCPP 1 +#endif + +#if (FIREBASE_COMPILER_CLANG && FIREBASE_STANDARD_LIBCPP) +#define FIREBASE_LINUX_BUILD_CONFIG_STRING "clang_libstdcpp" +#elif (FIREBASE_COMPILER_CLANG && FIREBASE_STANDARD_LIBSTDCPP) +#define FIREBASE_LINUX_BUILD_CONFIG_STRING "clang_libcpp" +#elif (FIREBASE_COMPILER_GCC && FIREBASE_STANDARD_LIBCPP) +#define FIREBASE_LINUX_BUILD_CONFIG_STRING "gcc_libstdcpp" +#elif (FIREBASE_COMPILER_GCC && FIREBASE_STANDARD_LIBSTDCPP) +#define FIREBASE_LINUX_BUILD_CONFIG_STRING "gcc_libcpp" +#else +#error "Unsupported compiler or standard library" +#endif + +#endif // FIREBASE_PLATFORM_LINUX + #define FIREBASE_PLATFORM_MOBILE \ (FIREBASE_PLATFORM_IOS || FIREBASE_PLATFORM_ANDROID) #define FIREBASE_PLATFORM_DESKTOP \ diff --git a/app/src/include/firebase/variant.h b/app/src/include/firebase/variant.h index 2547f4ea14..4314b0b426 100644 --- a/app/src/include/firebase/variant.h +++ b/app/src/include/firebase/variant.h @@ -18,11 +18,13 @@ #define FIREBASE_APP_CLIENT_CPP_SRC_INCLUDE_FIREBASE_VARIANT_H_ #include + #include #include #include #include #include + #include "firebase/internal/common.h" /// @brief Namespace that encompasses all Firebase APIs. @@ -30,6 +32,12 @@ #define FIREBASE_NAMESPACE firebase #endif +namespace firebase { +namespace internal { +class VariantInternal; +} +} + namespace FIREBASE_NAMESPACE { // @@ -67,6 +75,8 @@ class Variant { /// Variant::FromMutableBlob() to create a Variant of this type, and copy /// binary data from an existing source. kTypeMutableBlob, + + // Note: If you add new types update enum InternalType; }; // @@ -77,74 +87,52 @@ class Variant { /// @brief Construct a null Variant. /// /// The Variant constructed will be of type Null. - Variant() : type_(kTypeNull) {} - - /// @brief Construct a Variant containing the given 64-bit integer. - /// - /// The Variant constructed will be of type Int64. - /// - /// param[in] value The 64-bit integer value for the Variant. - Variant(int64_t value) : type_(kTypeNull) { set_int64_value(value); } + Variant() + : type_(kInternalTypeNull) + , value_({}) {} - /// @brief Construct a Variant containing the given integer. - /// - /// The Variant constructed will be of type Int64. + /// @brief Construct a Variant with the given templated type. /// - /// @param[in] value The 32-bit integer value for the Variant. - Variant(int value) : type_(kTypeNull) { - set_int64_value(static_cast(value)); - } - - /// @brief Construct a Variant containing the given double-precision floating - /// point value. + /// @param[in] value The value to construct the variant. /// - /// The Variant constructed will be of type Double. + /// Valid types for this constructor are `int`, `int64_t`, `float`, `double`, + /// `bool`, `const char*`, and `char*` (but see below for additional Variant + /// types). /// - /// @param[in] value The double-precision floating point value for the - /// Variant. - Variant(double value) : type_(kTypeNull) { set_double_value(value); } - - /// @brief Construct a Variant containing the given single-precision floating - /// point value. /// - /// The Variant constructed will be of type Double. + /// Type `int` or `int64_t`: + /// * The Variant constructed will be of type Int64. /// - /// @param[in] value The single-precision floating point value for the - /// Variant. - Variant(float value) : type_(kTypeNull) { - set_double_value(static_cast(value)); - } - - /// @brief Construct a Variant containing the given boolean value. + /// Type `double` or `float`: + /// * The Variant constructed will be of type Double. /// - /// The Variant constructed will be of type Bool. + /// Type `bool`: + /// * The Variant constructed will be of type Bool. /// - /// @param[in] value The boolean value for the Variant. - Variant(bool value) : type_(kTypeNull) { set_bool_value(value); } - - /// @brief Construct a Variant with the given static const string (no copy). + /// Type `const char*`: + /// * The Variant constructed will be of type StaticString, and is_string() + /// will return true. **Note:** If you use this constructor, you must + /// ensure that the memory pointed to stays valid for the life of the + /// Variant, otherwise call mutable_string() or set_mutable_string(), + /// which will copy the string to an internal buffer. /// - /// The Variant constructed will be of type StaticString, and is_string() will - /// return true. + /// Type `char*`: + /// * The Variant constructed will be of type MutableString, and is_string() + /// will return true. /// - /// @param[in] value A pointer to the static null-terminated string for the - /// Variant. - /// - /// @note If you use this constructor, you must ensure that the memory pointed - /// to stays valid for the life of the Variant, otherwise call - /// mutable_string() or set_mutable_string(), which will copy the string to an - /// internal buffer. - Variant(const char* value) : type_(kTypeNull) { set_string_value(value); } - - /// @brief Construct a Variant containing the given string value (makes a - /// copy). - /// - /// The Variant constructed will be of type MutableString, and is_string() - /// will return true. - /// - /// @param[in] value A pointer to a null-terminated string, which will be - /// copied into to the Variant. - Variant(char* value) : type_(kTypeNull) { set_mutable_string(value); } + /// Other types will result in compiler error unless using the following + /// constructor overloads: + /// * `std::string` + /// * `std::vector` + /// * `std::vector` where T is convertible to variant type + /// * `T*`, `size_t` where T is convertible to variant type + /// * `std::map` + /// * `std::map` where K and V is convertible to variant type + template + Variant(T value) // NOLINT + : type_(kInternalTypeNull) { + set_value_t(value); + } /// @brief Construct a Variant containing the given string value (makes a /// copy). @@ -153,7 +141,8 @@ class Variant { /// will return true. /// /// @param[in] value The string to use for the Variant. - Variant(const std::string& value) : type_(kTypeNull) { + Variant(const std::string& value) // NOLINT + : type_(kInternalTypeNull) { set_mutable_string(value); } @@ -162,7 +151,8 @@ class Variant { /// The Variant constructed will be of type Vector. /// /// @param[in] value The STL vector to copy into the Variant. - Variant(const std::vector& value) : type_(kTypeNull) { + Variant(const std::vector& value) // NOLINT + : type_(kInternalTypeNull) { set_vector(value); } @@ -175,11 +165,12 @@ class Variant { /// to Variant (such as ints, strings, vectors). A Variant will be created for /// each element, and copied into the Vector Variant constructed here. template - Variant(const std::vector& value) : type_(kTypeNull) { + Variant(const std::vector& value) // NOLINT + : type_(kInternalTypeNull) { Clear(kTypeVector); vector().reserve(value.size()); for (size_t i = 0; i < value.size(); i++) { - vector().push_back(Variant(value[i])); + vector().push_back(Variant(static_cast(value[i]))); } } @@ -193,7 +184,8 @@ class Variant { /// here. /// @param[in] array_size Number of elements of the array. template - Variant(const T array_of_values[], size_t array_size) : type_(kTypeNull) { + Variant(const T array_of_values[], size_t array_size) + : type_(kInternalTypeNull) { Clear(kTypeVector); vector().reserve(array_size); for (size_t i = 0; i < array_size; i++) { @@ -207,7 +199,8 @@ class Variant { /// The Variant constructed will be of type Map. /// /// @param[in] value The STL map to copy into the Variant. - Variant(const std::map& value) : type_(kTypeNull) { + Variant(const std::map& value) // NOLINT + : type_(kInternalTypeNull) { set_map(value); } @@ -222,7 +215,8 @@ class Variant { /// created for each key and for each value, and copied by pairs into the Map /// Variant constructed here. template - Variant(const std::map& value) : type_(kTypeNull) { + Variant(const std::map& value) // NOLINT + : type_(kInternalTypeNull) { Clear(kTypeMap); for (typename std::map::const_iterator i = value.begin(); i != value.end(); ++i) { @@ -233,7 +227,7 @@ class Variant { /// @brief Copy constructor. Performs a deep copy. /// /// @param[in] other Source Variant to copy from. - Variant(const Variant& other) : type_(kTypeNull) { *this = other; } + Variant(const Variant& other) : type_(kInternalTypeNull) { *this = other; } /// @brief Copy assignment operator. Performs a deep copy. /// @@ -246,13 +240,15 @@ class Variant { /// simply reassigning pointer ownership. /// /// @param[in] other Source Variant to move from. - Variant(Variant&& other) : type_(kTypeNull) { *this = std::move(other); } + Variant(Variant&& other) noexcept : type_(kInternalTypeNull) { + *this = std::move(other); + } /// @brief Move assignment operator. Efficiently moves the more complex data /// types by simply reassigning pointer ownership. /// /// @param[in] other Source Variant to move from. - Variant& operator=(Variant&& other); + Variant& operator=(Variant&& other) noexcept; #endif // defined(FIREBASE_USE_MOVE_OPERATORS) || defined(DOXYGEN) #endif // SWIG @@ -433,7 +429,15 @@ class Variant { /// @brief Get the current type contained in this Variant. /// /// @return The Variant's type. - Type type() const { return type_; } + Type type() const { + // To avoid breaking user code, alias the small string type to mutable + // string. + if (type_ == kInternalTypeSmallString) { + return kTypeMutableString; + } + + return static_cast(type_); + } /// @brief Get whether this Variant is currently null. /// @@ -478,11 +482,13 @@ class Variant { /// @brief Get whether this Variant contains a string. /// /// @return True if the Variant's type is either StaticString or - /// MutableString; false otherwise. + /// MutableString or SmallString; false otherwise. /// /// @note No matter which type of string the Variant contains, you can read /// its value via string_value(). - bool is_string() const { return is_static_string() || is_mutable_string(); } + bool is_string() const { + return is_static_string() || is_mutable_string() || is_small_string(); + } /// @brief Get whether this Variant contains a static blob. /// @@ -574,9 +580,10 @@ class Variant { /// /// @note If the Variant is not one of the two String types, this will assert. std::string& mutable_string() { - if (type_ == kTypeStaticString) { - // Automatically promote a static string to a mutable string. - set_mutable_string(string_value()); + if (type_ == kInternalTypeStaticString || + type_ == kInternalTypeSmallString) { + // Automatically promote a static or small string to a mutable string. + set_mutable_string(string_value(), false); } assert_is_type(kTypeMutableString); return *value_.mutable_string_value; @@ -610,7 +617,7 @@ class Variant { /// @returns Pointer to a mutable buffer of binary data. The size of the /// buffer cannot be changed, but the contents are mutable. uint8_t* mutable_blob_data() { - if (type_ == kTypeStaticBlob) { + if (type_ == kInternalTypeStaticBlob) { // Automatically promote a static blob to a mutable blob. set_mutable_blob(blob_data(), blob_size()); } @@ -692,22 +699,24 @@ class Variant { /// will assert. const char* string_value() const { assert_is_string(); - if (type_ == kTypeMutableString) + if (type_ == kInternalTypeMutableString) return value_.mutable_string_value->c_str(); - else // type_ == kTypeStaticString + else if (type_ == kInternalTypeStaticString) return value_.static_string_value; + else // if (type_ == kInternalTypeSmallString) + return value_.small_string; } - /// @brief Const accessor for a Variant containing a mutable string only. + /// @brief Const accessor for a Variant containing a string. /// /// @note Unlike the non-const accessor, this accessor cannot "promote" a - /// static string to mutable, and thus will assert if the Variant you pass in - /// is not of MutableString type. + /// static string to mutable, and thus returns a std::string copy instead of a + /// const reference to a std::string /// - /// @return Reference to the string contained in this Variant. - const std::string& mutable_string() const { - assert_is_type(kTypeMutableString); - return *value_.mutable_string_value; + /// @return std::string with the string contents contained in this Variant. + std::string mutable_string() const { + assert_is_string(); + return string_value(); } /// @brief Const accessor for a Variant containing a vector of Variant data. @@ -790,7 +799,15 @@ class Variant { /// /// @param[in] value A pointer to a null-terminated string, which will be /// copied into to the Variant. - void set_string_value(char* value) { set_mutable_string(value); } + void set_string_value(char* value) { + size_t len = strlen(value); + if (len < kMaxSmallStringSize) { + Clear(static_cast(kInternalTypeSmallString)); + strncpy(value_.small_string, value, len + 1); + } else { + set_mutable_string(std::string(value, len)); + } + } /// @brief Sets the Variant to a mutable string. /// @@ -801,12 +818,22 @@ class Variant { /// @brief Sets the Variant to a copy of the given string. /// - /// The Variant's type will be set to MutableString. + /// The Variant's type will be set to SmallString if the size of the string is + /// less than kMaxSmallStringSize (8 bytes on x86, 16 bytes on x64) or + /// otherwise set to MutableString. /// /// @param[in] value The string to use for the Variant. - void set_mutable_string(const std::string& value) { - Clear(kTypeMutableString); - *value_.mutable_string_value = value; + /// @param[in] use_small_string Check to see if the input string should be + /// treated as a small string or left as a mutable string + void set_mutable_string(const std::string& value, + bool use_small_string = true) { + if (value.size() < kMaxSmallStringSize && use_small_string) { + Clear(static_cast(kInternalTypeSmallString)); + strncpy(value_.small_string, value.data(), value.size() + 1); + } else { + Clear(kTypeMutableString); + *value_.mutable_string_value = value; + } } /// @brief Sets the Variant to a copy of the given binary data. @@ -877,7 +904,7 @@ class Variant { /// you passed in to NULL. void AssignMutableString(std::string** str) { Clear(kTypeNull); - type_ = kTypeMutableString; + type_ = kInternalTypeMutableString; value_.mutable_string_value = *str; *str = NULL; // NOLINT } @@ -894,7 +921,7 @@ class Variant { /// you passed in to NULL. void AssignVector(std::vector** vect) { Clear(kTypeNull); - type_ = kTypeVector; + type_ = kInternalTypeVector; value_.vector_value = *vect; *vect = NULL; // NOLINT } @@ -911,7 +938,7 @@ class Variant { /// passed in to NULL. void AssignMap(std::map** map) { Clear(kTypeNull); - type_ = kTypeMap; + type_ = kInternalTypeMap; value_.map_value = *map; *map = NULL; // NOLINT } @@ -1017,6 +1044,40 @@ class Variant { static const char* TypeName(Type type); private: + // Internal Type of data that this variant object contains to avoid breaking + // API + enum InternalType { + /// Null, or no data. + kInternalTypeNull = kTypeNull, + /// A 64-bit integer. + kInternalTypeInt64 = kTypeInt64, + /// A double-precision floating point number. + kInternalTypeDouble = kTypeDouble, + /// A boolean value. + kInternalTypeBool = kTypeBool, + /// A statically-allocated string we point to. + kInternalTypeStaticString = kTypeStaticString, + /// A std::string. + kInternalTypeMutableString = kTypeMutableString, + /// A std::vector of Variant. + kInternalTypeVector = kTypeVector, + /// A std::map, mapping Variant to Variant. + kInternalTypeMap = kTypeMap, + /// An statically-allocated blob of data that we point to. Never constructed + /// by default. Use Variant::FromStaticBlob() to create a Variant of this + /// type. + kInternalTypeStaticBlob = kTypeStaticBlob, + /// A blob of data that the Variant holds. Never constructed by default. Use + /// Variant::FromMutableBlob() to create a Variant of this type, and copy + /// binary data from an existing source. + kInternalTypeMutableBlob = kTypeMutableBlob, + // A c string stored in the Variant internal data blob as opposed to be + // newed as a std::string. Max size is 16 bytes on x64 and 8 bytes on x86. + kInternalTypeSmallString = kTypeMutableBlob + 1, + // Not a valid type. Used to get the total number of Variant types. + kMaxTypeValue, + }; + /// Human-readable type names, for error logging. static const char* const kTypeNames[]; @@ -1043,8 +1104,28 @@ class Variant { value_.blob_value.size = size; } + // Templated helper function to ensure the value 0 is constructed as int + // instead of nullptr char* + // + // If you hit a compiler error here it means you are trying to construct a + // variant with unsupported type. Ether cast to correct type or add support + // below. + template + void set_value_t(T value); + + // Get whether this Variant contains a small string. + bool is_small_string() const { return type_ == kInternalTypeSmallString; } + // Current type contained in this Variant. - Type type_; + InternalType type_; + + // Older versions of visual studio cant have this inline in the union and do + // sizeof for small string + typedef struct { + const uint8_t* ptr; + size_t size; + } BlobValue; + // Union of plain old data (scalars or pointers). union Value { int64_t int64_value; @@ -1054,13 +1135,70 @@ class Variant { std::string* mutable_string_value; std::vector* vector_value; std::map* map_value; - struct { - const uint8_t* ptr; - size_t size; - } blob_value; + BlobValue blob_value; + char small_string[sizeof(BlobValue)]; } value_; + + static const size_t kMaxSmallStringSize = sizeof(Value::small_string); + + friend class firebase::internal::VariantInternal; }; +template <> +inline void Variant::set_value_t(int64_t value) { + set_int64_value(value); +} + +template <> +inline void Variant::set_value_t(int value) { + set_int64_value(static_cast(value)); +} + +template <> +inline void Variant::set_value_t(int16_t value) { + set_int64_value(static_cast(value)); +} + +template <> +inline void Variant::set_value_t(uint8_t value) { + set_int64_value(static_cast(value)); +} + +template <> +inline void Variant::set_value_t(int8_t value) { + set_int64_value(static_cast(value)); +} + +template <> +inline void Variant::set_value_t(char value) { + set_int64_value(static_cast(value)); +} + +template <> +inline void Variant::set_value_t(double value) { + set_double_value(value); +} + +template <> +inline void Variant::set_value_t(float value) { + set_double_value(static_cast(value)); +} + +template <> +inline void Variant::set_value_t(bool value) { + set_bool_value(value); +} + +template <> +inline void Variant::set_value_t(const char* value) { + set_string_value(value); +} + +template <> +inline void Variant::set_value_t(char* value) { + set_mutable_string(value); +} + // NOLINTNEXTLINE - allow namespace overridden } // namespace FIREBASE_NAMESPACE diff --git a/app/src/intrusive_list.h b/app/src/intrusive_list.h index ab20f5e46e..3a3759ad43 100644 --- a/app/src/intrusive_list.h +++ b/app/src/intrusive_list.h @@ -251,9 +251,9 @@ class intrusive_list { : data_(&data_, &data_), node_offset_(offset_of_node(node_member)) {} #if defined(FIREBASE_USE_MOVE_OPERATORS) - intrusive_list(this_type&& other) { *this = std::move(other); } + intrusive_list(this_type&& other) noexcept { *this = std::move(other); } - intrusive_list& operator=(this_type&& other) { + intrusive_list& operator=(this_type&& other) noexcept { data_ = std::move(other.data_); node_offset_ = std::move(other.node_offset_); return *this; diff --git a/app/src/jobject_reference.cc b/app/src/jobject_reference.cc index 18af9dc923..21b23738e2 100644 --- a/app/src/jobject_reference.cc +++ b/app/src/jobject_reference.cc @@ -39,7 +39,7 @@ JObjectReference::JObjectReference(const JObjectReference& reference) { } #ifdef FIREBASE_USE_MOVE_OPERATORS -JObjectReference::JObjectReference(JObjectReference&& reference) { +JObjectReference::JObjectReference(JObjectReference&& reference) noexcept { operator=(std::move(reference)); } #endif // FIREBASE_USE_MOVE_OPERATORS @@ -53,7 +53,8 @@ JObjectReference& JObjectReference::operator=( } #ifdef FIREBASE_USE_MOVE_OPERATORS -JObjectReference& JObjectReference::operator=(JObjectReference&& reference) { +JObjectReference& JObjectReference::operator=(JObjectReference&& reference) + noexcept { java_vm_ = reference.java_vm_; object_ = reference.object_; reference.java_vm_ = nullptr; @@ -100,9 +101,10 @@ void JObjectReference::Initialize(JavaVM* jvm, JNIEnv* env, } JavaVM* JObjectReference::GetJavaVM(JNIEnv* env) { - JavaVM* jvm; + FIREBASE_DEV_ASSERT(env); + JavaVM* jvm = nullptr; jint result = env->GetJavaVM(&jvm); - assert(result == JNI_OK); + FIREBASE_DEV_ASSERT(result == JNI_OK); return jvm; } diff --git a/app/src/jobject_reference.h b/app/src/jobject_reference.h index fc12fa514a..c7bcb0506d 100644 --- a/app/src/jobject_reference.h +++ b/app/src/jobject_reference.h @@ -66,7 +66,7 @@ class JObjectReference { JObjectReference(const JObjectReference& reference); // Move #ifdef FIREBASE_USE_MOVE_OPERATORS - JObjectReference(JObjectReference&& reference); + JObjectReference(JObjectReference&& reference) noexcept; #endif // FIREBASE_USE_MOVE_OPERATORS // Delete the reference to the java object. ~JObjectReference(); @@ -74,7 +74,7 @@ class JObjectReference { JObjectReference& operator=(const JObjectReference& reference); // Move this reference. #ifdef FIREBASE_USE_MOVE_OPERATORS - JObjectReference& operator=(JObjectReference&& reference); + JObjectReference& operator=(JObjectReference&& reference) noexcept; #endif // FIREBASE_USE_MOVE_OPERATORS // Add a global reference to the specified object, removing the reference diff --git a/app/src/optional.h b/app/src/optional.h index f557eca9de..db2fc7531c 100644 --- a/app/src/optional.h +++ b/app/src/optional.h @@ -69,7 +69,7 @@ class Optional { #if defined(FIREBASE_USE_MOVE_OPERATORS) // Move contructor. If the other Optional has a value, it is moved into this // Optional using its move constructor. - Optional(Optional&& other) : has_value_(other.has_value_) { + Optional(Optional&& other) noexcept : has_value_(other.has_value_) { if (has_value()) { new (aligned_buffer()) value_type(std::move(other.value())); other.reset(); @@ -78,7 +78,7 @@ class Optional { // Move assignment. If the other Optional has a value, it is move constructed // or move assigned into this Optional. - Optional& operator=(Optional&& other) { + Optional& operator=(Optional&& other) noexcept { if (other.has_value()) { *this = std::move(other.value()); } else { diff --git a/app/src/util_android.cc b/app/src/util_android.cc index bcd2b6488e..206fc817c2 100644 --- a/app/src/util_android.cc +++ b/app/src/util_android.cc @@ -242,11 +242,11 @@ static std::vector* g_class_loaders; JNIEXPORT void JNICALL JniResultCallback_nativeOnResult( JNIEnv* env, jobject clazz, jobject result, jboolean success, - jboolean cancelled, jint status, jstring status_message, - jlong callback_fn_param, jlong callback_data); + jboolean cancelled, jstring status_message, jlong callback_fn_param, + jlong callback_data); static const JNINativeMethod kJniCallbackMethod = { - "nativeOnResult", "(Ljava/lang/Object;ZZILjava/lang/String;JJ)V", + "nativeOnResult", "(Ljava/lang/Object;ZZLjava/lang/String;JJ)V", reinterpret_cast(JniResultCallback_nativeOnResult)}; static const JNINativeMethod kNativeLogMethods[] = { @@ -1252,8 +1252,8 @@ void RegisterCallbackOnTask(JNIEnv* env, jobject task, JNIEXPORT void JNICALL JniResultCallback_nativeOnResult( JNIEnv* env, jobject clazz, jobject result, jboolean success, - jboolean cancelled, jint status, jstring status_message, - jlong callback_fn_param, jlong callback_data) { + jboolean cancelled, jstring status_message, jlong callback_fn_param, + jlong callback_data) { void* user_callback_data; pthread_mutex_lock(&g_task_callbacks_mutex); { @@ -1282,8 +1282,8 @@ JNIEXPORT void JNICALL JniResultCallback_nativeOnResult( FutureResult result_code = success ? kFutureResultSuccess : (cancelled ? kFutureResultCancelled : kFutureResultFailure); - callback_fn(env, result, result_code, static_cast(status), - status_message_c.c_str(), user_callback_data); + callback_fn(env, result, result_code, status_message_c.c_str(), + user_callback_data); } // Call a C++ function pointer, passing in a given data pointer. This is called diff --git a/app/src/util_android.h b/app/src/util_android.h index f111d7b95d..8e192d78eb 100644 --- a/app/src/util_android.h +++ b/app/src/util_android.h @@ -954,11 +954,10 @@ enum FutureResult { // @param result The result referred to by `task` in the // call to RegisterCallbackOnTask(). // @param result_code The result of the Future (Success, Failure, Cancelled). -// @param status This value is 0 but present for historical reasons. // @param callback_data Passed through verbatim from // RegisterCallbackOnTask(). typedef void TaskCallbackFn(JNIEnv* env, jobject result, - FutureResult result_code, int status, + FutureResult result_code, const char* status_message, void* callback_data); // Calls callback_fn when the Task `task` is complete, where `task` is an diff --git a/app/src/variant.cc b/app/src/variant.cc index 977f686610..d765c95a3f 100644 --- a/app/src/variant.cc +++ b/app/src/variant.cc @@ -29,49 +29,56 @@ namespace firebase { Variant& Variant::operator=(const Variant& other) { if (this != &other) { - Clear(other.type()); + Clear(static_cast(other.type_)); switch (type_) { - case kTypeNull: { + case kInternalTypeNull: { break; } - case kTypeInt64: { + case kInternalTypeInt64: { set_int64_value(other.int64_value()); break; } - case kTypeDouble: { + case kInternalTypeDouble: { set_double_value(other.double_value()); break; } - case kTypeBool: { + case kInternalTypeBool: { set_bool_value(other.bool_value()); break; } - case kTypeStaticString: { + case kInternalTypeStaticString: { set_string_value(other.string_value()); break; } - case kTypeMutableString: { + case kInternalTypeMutableString: { set_mutable_string(other.mutable_string()); break; } - case kTypeVector: { + case kInternalTypeSmallString: { + strcpy(value_.small_string, other.value_.small_string); // NOLINT + break; + } + case kInternalTypeVector: { set_vector(other.vector()); break; } - case kTypeMap: { + case kInternalTypeMap: { set_map(other.map()); break; } - case kTypeStaticBlob: { + case kInternalTypeStaticBlob: { set_blob_pointer(other.value_.blob_value.ptr, other.value_.blob_value.size); break; } - case kTypeMutableBlob: { + case kInternalTypeMutableBlob: { set_mutable_blob(other.value_.blob_value.ptr, other.value_.blob_value.size); break; } + case kMaxTypeValue: + FIREBASE_ASSERT(false); // Should never hit this + break; } } return *this; @@ -79,59 +86,68 @@ Variant& Variant::operator=(const Variant& other) { #if defined(FIREBASE_USE_MOVE_OPERATORS) -Variant& Variant::operator=(Variant&& other) { +Variant& Variant::operator=(Variant&& other) noexcept { if (this != &other) { Clear(); type_ = other.type_; - other.type_ = kTypeNull; + other.type_ = kInternalTypeNull; switch (type_) { - case kTypeNull: { + case kInternalTypeNull: { break; } - case kTypeInt64: { + case kInternalTypeInt64: { value_.int64_value = other.value_.int64_value; break; } - case kTypeDouble: { + case kInternalTypeDouble: { value_.double_value = other.value_.double_value; break; } - case kTypeBool: { + case kInternalTypeBool: { value_.bool_value = other.value_.bool_value; break; } - case kTypeStaticString: { + case kInternalTypeStaticString: { value_.static_string_value = other.value_.static_string_value; other.value_.static_string_value = nullptr; break; } - case kTypeMutableString: { + case kInternalTypeMutableString: { value_.mutable_string_value = other.value_.mutable_string_value; other.value_.mutable_string_value = nullptr; break; } - case kTypeVector: { + case kInternalTypeSmallString: { + memcpy(value_.small_string, other.value_.small_string, + kMaxSmallStringSize); + other.value_.small_string[0] = '\0'; + break; + } + case kInternalTypeVector: { value_.vector_value = other.value_.vector_value; other.value_.vector_value = nullptr; break; } - case kTypeMap: { + case kInternalTypeMap: { value_.map_value = other.value_.map_value; other.value_.map_value = nullptr; break; } - case kTypeStaticBlob: { + case kInternalTypeStaticBlob: { set_static_blob(other.value_.blob_value.ptr, other.value_.blob_value.size); break; } - case kTypeMutableBlob: { + case kInternalTypeMutableBlob: { set_blob_pointer(other.value_.blob_value.ptr, other.value_.blob_value.size); other.value_.blob_value.ptr = nullptr; other.value_.blob_value.size = 0; break; } + case kMaxTypeValue: + FIREBASE_ASSERT(false); // Should never hit this + break; } } return *this; @@ -141,65 +157,96 @@ Variant& Variant::operator=(Variant&& other) { bool Variant::operator==(const Variant& other) const { // If types don't match and we aren't both strings or blobs, fail. - if (type() != other.type() && !(is_string() && other.is_string()) && + if (type_ != other.type_ && !(is_string() && other.is_string()) && !(is_blob() && other.is_blob())) return false; // Now we know their types are equivalent. So: - switch (type()) { - case kTypeNull: + switch (type_) { + case kInternalTypeNull: return true; // nulls are always equal - case kTypeInt64: + case kInternalTypeInt64: return int64_value() == other.int64_value(); - case kTypeDouble: + case kInternalTypeDouble: return double_value() == other.double_value(); - case kTypeBool: + case kInternalTypeBool: return bool_value() == other.bool_value(); - case kTypeMutableString: - case kTypeStaticString: + case kInternalTypeMutableString: + case kInternalTypeStaticString: + case kInternalTypeSmallString: // string == performs string comparison return strcmp(string_value(), other.string_value()) == 0; - case kTypeVector: + case kInternalTypeVector: // std::vector == performs element-by-element comparison return vector() == other.vector(); - case kTypeMap: + case kInternalTypeMap: // std::map == performs element-by-element comparison return map() == other.map(); - case kTypeStaticBlob: - case kTypeMutableBlob: + case kInternalTypeStaticBlob: + case kInternalTypeMutableBlob: // Return true if both are static blobs with the same pointers, otherwise // compare the contents. Also the sizes must match. return blob_size() == other.blob_size() && ((is_static_blob() && other.is_static_blob() && blob_data() == other.blob_data()) || memcmp(blob_data(), other.blob_data(), blob_size()) == 0); + case kMaxTypeValue: + FIREBASE_ASSERT(false); // Should never hit this + break; } return false; // Should never reach this. } bool Variant::operator<(const Variant& other) const { + Type left_type = type(); + Type right_type = other.type(); + + // If we are any string type set type to static string as we care about string + // value not type of string. + if (is_string()) { + left_type = kTypeStaticString; + } + + // If other is any string type set type to static string as we care about + // string value not type of string. + if (other.is_string()) { + right_type = kTypeStaticString; + } + + // If we are any blob type set type to static blob as we care about blob value + // not type of blob. + if (is_blob()) { + left_type = kTypeStaticBlob; + } + + // If other is any blob type set type to static blob as we care about blob + // value not type of blob. + if (other.is_blob()) { + right_type = kTypeStaticBlob; + } + // If the types don't match (except count both string types as matching, and // both blob types as matching), compare the types. - if (type() != other.type() && !(is_string() && other.is_string()) && - !(is_blob() && other.is_blob())) - return static_cast(type()) < static_cast(other.type()); + if (left_type != right_type) + return static_cast(left_type) < static_cast(right_type); // Type is now equal (or both strings, or both blobs). switch (type_) { - case kTypeNull: { + case kInternalTypeNull: { return false; // nulls are always equal } - case kTypeInt64: { + case kInternalTypeInt64: { return int64_value() < other.int64_value(); } - case kTypeDouble: { + case kInternalTypeDouble: { return double_value() < other.double_value(); } - case kTypeBool: { + case kInternalTypeBool: { return bool_value() < other.bool_value(); } - case kTypeMutableString: - case kTypeStaticString: + case kInternalTypeMutableString: + case kInternalTypeStaticString: + case kInternalTypeSmallString: return strcmp(string_value(), other.string_value()) < 0; - case kTypeVector: { + case kInternalTypeVector: { auto i = vector().begin(); auto j = other.vector().begin(); for (; i != vector().end() && j != other.vector().end(); ++i, ++j) { @@ -211,7 +258,7 @@ bool Variant::operator<(const Variant& other) const { if (i != vector().end() && j == other.vector().end()) return false; return false; // Equal! } - case kTypeMap: { + case kInternalTypeMap: { auto i = map().begin(); auto j = other.map().begin(); for (; i != map().end() && j != other.map().end(); ++i, ++j) { @@ -224,122 +271,165 @@ bool Variant::operator<(const Variant& other) const { if (i != map().end() && j == other.map().end()) return false; return false; // Equal! } - case kTypeMutableBlob: - case kTypeStaticBlob: + case kInternalTypeMutableBlob: + case kInternalTypeStaticBlob: return blob_size() == other.blob_size() ? memcmp(blob_data(), other.blob_data(), blob_size()) < 0 : blob_size() < other.blob_size(); + case kMaxTypeValue: + FIREBASE_ASSERT(false); // Should never hit this + break; } return false; // Should never reach this. } void Variant::Clear(Type new_type) { switch (type_) { - case kTypeNull: { + case kInternalTypeNull: { break; } - case kTypeInt64: { + case kInternalTypeInt64: { value_.int64_value = 0; break; } - case kTypeDouble: { + case kInternalTypeDouble: { value_.double_value = 0; break; } - case kTypeBool: { + case kInternalTypeBool: { value_.bool_value = false; break; } - case kTypeStaticString: { + case kInternalTypeStaticString: { value_.static_string_value = nullptr; break; } - case kTypeMutableString: { - delete value_.mutable_string_value; - value_.mutable_string_value = nullptr; + case kInternalTypeMutableString: { + if (new_type != kTypeMutableString + || value_.mutable_string_value == nullptr) { + delete value_.mutable_string_value; + value_.mutable_string_value = nullptr; + } else { + value_.mutable_string_value->clear(); + } + break; + } + case kInternalTypeSmallString: { + value_.small_string[0] = '\0'; break; } - case kTypeVector: { - delete value_.vector_value; - value_.vector_value = nullptr; + case kInternalTypeVector: { + if (new_type != kTypeVector || value_.vector_value == nullptr) { + delete value_.vector_value; + value_.vector_value = nullptr; + } else { + value_.vector_value->clear(); + } break; } - case kTypeMap: { - delete value_.map_value; - value_.map_value = nullptr; + case kInternalTypeMap: { + if (new_type != kTypeMap || value_.map_value == nullptr) { + delete value_.map_value; + value_.map_value = nullptr; + } else { + value_.map_value->clear(); + } break; } - case kTypeStaticBlob: { + case kInternalTypeStaticBlob: { set_blob_pointer(nullptr, 0); break; } - case kTypeMutableBlob: { + case kInternalTypeMutableBlob: { uint8_t* prev_data = const_cast(value_.blob_value.ptr); set_blob_pointer(nullptr, 0); delete[] prev_data; break; } + case kMaxTypeValue: + FIREBASE_ASSERT(false); // Should never hit this + break; } - type_ = new_type; + + InternalType old_type = type_; + type_ = static_cast(new_type); switch (type_) { - case kTypeNull: { + case kInternalTypeNull: { break; } - case kTypeInt64: { + case kInternalTypeInt64: { value_.int64_value = 0; break; } - case kTypeDouble: { + case kInternalTypeDouble: { value_.double_value = 0; break; } - case kTypeBool: { + case kInternalTypeBool: { value_.bool_value = false; break; } - case kTypeStaticString: { + case kInternalTypeStaticString: { value_.static_string_value = ""; break; } - case kTypeMutableString: { - value_.mutable_string_value = new std::string(); + case kInternalTypeMutableString: { + if (old_type != kInternalTypeMutableString || + value_.mutable_string_value == nullptr) { + value_.mutable_string_value = new std::string(); + } + break; + } + case kInternalTypeSmallString: { + value_.small_string[0] = '\0'; break; } - case kTypeVector: { - value_.vector_value = new std::vector(0); + case kInternalTypeVector: { + if (old_type != kInternalTypeVector || value_.vector_value == nullptr) { + value_.vector_value = new std::vector(0); + } break; } - case kTypeMap: { - value_.map_value = new std::map(); + case kInternalTypeMap: { + if (old_type != kInternalTypeMap || value_.map_value == nullptr) { + value_.map_value = new std::map(); + } break; } - case kTypeStaticBlob: { + case kInternalTypeStaticBlob: { set_blob_pointer(nullptr, 0); break; } - case kTypeMutableBlob: { + case kInternalTypeMutableBlob: { set_blob_pointer(nullptr, 0); break; } + case kMaxTypeValue: + FIREBASE_ASSERT(false); // Should never hit this + break; } } const char* const Variant::kTypeNames[] = { // In case you want to iterate through these for some reason. - "Null", "Int64", "Double", "Bool", - "StaticString", "MutableString", "Vector", "Map", - "StaticBlob", "MutableBlob", nullptr, + "Null", "Int64", "Double", "Bool", + "StaticString", "MutableString", "Vector", "Map", + "StaticBlob", "MutableBlob", "SmallString", nullptr, }; void Variant::assert_is_type(Variant::Type type) const { + static_assert(FIREBASE_ARRAYSIZE(Variant::kTypeNames) == + Variant::kMaxTypeValue + 1, + "Type Enum should match kTypeNames"); + FIREBASE_ASSERT_MESSAGE( - this->type() == type, + this->type_ == static_cast(type), "Expected Variant to be of type %s, but it was of type %s.", - kTypeNames[type], kTypeNames[this->type()]); + kTypeNames[type], kTypeNames[this->type_]); } void Variant::assert_is_not_type(Variant::Type type) const { - FIREBASE_ASSERT_MESSAGE(this->type() == type, + FIREBASE_ASSERT_MESSAGE(this->type_ == static_cast(type), "Expected Variant to NOT be of type %s, but it is.", kTypeNames[type]); } @@ -347,13 +437,13 @@ void Variant::assert_is_not_type(Variant::Type type) const { void Variant::assert_is_string() const { FIREBASE_ASSERT_MESSAGE( is_string(), "Expected Variant to be a String, but it was of type %s.", - kTypeNames[this->type()]); + kTypeNames[this->type_]); } void Variant::assert_is_blob() const { FIREBASE_ASSERT_MESSAGE( is_blob(), "Expected Variant to be a Blob, but it was of type %s.", - kTypeNames[this->type()]); + kTypeNames[this->type_]); } #if FIREBASE_PLATFORM_WINDOWS @@ -367,7 +457,7 @@ Variant Variant::AsString() const { static const size_t kBufferSize = 64; #endif // FIREBASE_USE_SNPRINTF switch (type_) { - case kTypeInt64: { + case kInternalTypeInt64: { #ifdef FIREBASE_USE_SNPRINTF char buffer[kBufferSize]; snprintf(buffer, kBufferSize, INT64FORMAT, @@ -379,7 +469,7 @@ Variant Variant::AsString() const { return Variant::FromMutableString(ss.str()); #endif // FIREBASE_USE_SNPRINTF } - case kTypeDouble: { + case kInternalTypeDouble: { #ifdef FIREBASE_USE_SNPRINTF char buffer[kBufferSize]; snprintf(buffer, kBufferSize, "%.16f", double_value()); @@ -390,11 +480,12 @@ Variant Variant::AsString() const { return Variant::FromMutableString(ss.str()); #endif // FIREBASE_USE_SNPRINTF } - case kTypeBool: { + case kInternalTypeBool: { return bool_value() ? Variant("true") : Variant("false"); } - case kTypeMutableString: - case kTypeStaticString: { + case kInternalTypeMutableString: + case kInternalTypeStaticString: + case kInternalTypeSmallString: { return *this; } default: { @@ -405,17 +496,18 @@ Variant Variant::AsString() const { Variant Variant::AsInt64() const { switch (type_) { - case kTypeInt64: { + case kInternalTypeInt64: { return *this; } - case kTypeDouble: { + case kInternalTypeDouble: { return Variant::FromInt64(static_cast(double_value())); } - case kTypeBool: { + case kInternalTypeBool: { return bool_value() ? Variant::One() : Variant::Zero(); } - case kTypeMutableString: - case kTypeStaticString: { + case kInternalTypeMutableString: + case kInternalTypeStaticString: + case kInternalTypeSmallString: { return Variant::FromInt64(strtol(string_value(), nullptr, 10)); // NOLINT } default: { @@ -426,17 +518,18 @@ Variant Variant::AsInt64() const { Variant Variant::AsDouble() const { switch (type_) { - case kTypeInt64: { + case kInternalTypeInt64: { return Variant::FromDouble(static_cast(int64_value())); } - case kTypeDouble: { + case kInternalTypeDouble: { return *this; } - case kTypeBool: { + case kInternalTypeBool: { return bool_value() ? Variant::OnePointZero() : Variant::ZeroPointZero(); } - case kTypeMutableString: - case kTypeStaticString: { + case kInternalTypeMutableString: + case kInternalTypeStaticString: + case kInternalTypeSmallString: { return Variant::FromDouble(strtod(string_value(), nullptr)); } default: { diff --git a/app/src/variant_util.cc b/app/src/variant_util.cc index ce05e26b49..eadb7428d9 100644 --- a/app/src/variant_util.cc +++ b/app/src/variant_util.cc @@ -15,7 +15,10 @@ */ #include "app/src/variant_util.h" + #include + +#include "app/src/assert.h" #include "app/src/log.h" #include "flatbuffers/flatbuffers.h" #include "flatbuffers/flexbuffers.h" diff --git a/app/src/variant_util.h b/app/src/variant_util.h index 8ef0382dad..bc6bda65b0 100644 --- a/app/src/variant_util.h +++ b/app/src/variant_util.h @@ -18,6 +18,7 @@ #define FIREBASE_APP_CLIENT_CPP_SRC_VARIANT_UTIL_H_ #include + #include "app/src/include/firebase/variant.h" #include "flatbuffers/flexbuffers.h" @@ -48,7 +49,7 @@ Variant FlexbufferToVariant(const flexbuffers::Reference& ref); Variant FlexbufferMapToVariant(const flexbuffers::Map& map); // Convert from a Flexbuffer vector to a Variant. -Variant FlexbufferVectorToVariant(const flexbuffers::Vector& map); +Variant FlexbufferVectorToVariant(const flexbuffers::Vector& vector); // Convert from a Variant to a Flexbuffer buffer. std::vector VariantToFlexbuffer(const Variant& variant); diff --git a/app/src_java/com/google/firebase/app/internal/cpp/JniResultCallback.java b/app/src_java/com/google/firebase/app/internal/cpp/JniResultCallback.java index 354533e4f1..f3b2a79a0d 100644 --- a/app/src_java/com/google/firebase/app/internal/cpp/JniResultCallback.java +++ b/app/src_java/com/google/firebase/app/internal/cpp/JniResultCallback.java @@ -35,7 +35,7 @@ private interface Callback { * Remove this class from the callback. */ public void disconnect(); - }; + } // C++ pointers. Use `long` type since it is a 64-bit integer. private long callbackFn; @@ -64,7 +64,7 @@ public TaskCallback(Task task) { public void onSuccess(TResult result) { synchronized (lockObject) { if (task != null) { - onCompletion(result, true, false, 0, null); + onCompletion(result, true, false, null); } disconnect(); } @@ -74,7 +74,7 @@ public void onSuccess(TResult result) { public void onFailure(Exception exception) { synchronized (lockObject) { if (task != null) { - onCompletion(exception, false, false, 0, exception.getMessage()); + onCompletion(exception, false, false, exception.getMessage()); } disconnect(); } @@ -140,16 +140,15 @@ protected void initializeWithTask(Task task) { */ public void cancel() { // Complete the native callback to free data associated with callbackData. - onCompletion(null, false, true, -1, "cancelled"); + onCompletion(null, false, true, "cancelled"); } /** Call nativeOnResult with the registered callbackFn and callbackData. */ public void onCompletion( - Object result, boolean success, boolean cancelled, int statusCode, String statusMessage) { + Object result, boolean success, boolean cancelled, String statusMessage) { synchronized (this) { if (callbackHandler != null) { - nativeOnResult( - result, success, cancelled, statusCode, statusMessage, callbackFn, callbackData); + nativeOnResult(result, success, cancelled, statusMessage, callbackFn, callbackData); callbackHandler.disconnect(); callbackHandler = null; } @@ -161,7 +160,6 @@ private native void nativeOnResult( Object result, boolean success, boolean cancelled, - int status, String statusString, long callbackFn, long callbackData); diff --git a/auth/CMakeLists.txt b/auth/CMakeLists.txt index a6246576c4..3ad59da986 100644 --- a/auth/CMakeLists.txt +++ b/auth/CMakeLists.txt @@ -19,6 +19,7 @@ set (CMAKE_CXX_STANDARD 11) include(binary_to_array) include(download_pod_headers) +include(firebase_cpp_gradle) project(firebase_auth NONE) enable_language(C) @@ -64,6 +65,8 @@ set(common_SRCS src/user.cc) # Define the resource build needed for Android +firebase_cpp_gradle(":auth:auth_resources:generateDexJarRelease" + "${CMAKE_CURRENT_LIST_DIR}/auth_resources/build/dexed.jar") binary_to_array("auth_resources" "${CMAKE_CURRENT_LIST_DIR}/auth_resources/build/dexed.jar" "firebase_auth" @@ -196,7 +199,9 @@ if(MSVC) add_definitions(-DNOMINMAX) endif() -if(IOS) +if(ANDROID) + firebase_cpp_proguard_file(auth) +elseif(IOS) # Enable Automatic Reference Counting (ARC). set_property( TARGET firebase_auth @@ -207,8 +212,8 @@ if(IOS) set(pod_target_name "download_auth_pod_headers") set(pods_dir "${PROJECT_BINARY_DIR}/Pods") set(pod_list "") - list(APPEND pod_list "'Firebase/Core', '6.9.0'") - list(APPEND pod_list "'Firebase/Auth', '6.9.0'") + list(APPEND pod_list "'Firebase/Core', '6.10.0'") + list(APPEND pod_list "'Firebase/Auth', '6.10.0'") setup_pod_headers_target("${pod_target_name}" "${pods_dir}" "${pod_list}") diff --git a/auth/auth_resources/build.gradle b/auth/auth_resources/build.gradle index 010c82eb25..833177ac82 100644 --- a/auth/auth_resources/build.gradle +++ b/auth/auth_resources/build.gradle @@ -46,7 +46,7 @@ android { dependencies { implementation 'com.google.firebase:firebase-analytics:17.2.0' - implementation 'com.google.firebase:firebase-auth:19.0.0' + implementation 'com.google.firebase:firebase-auth:19.1.0' implementation project(':app:app_resources') } diff --git a/auth/src/android/common_android.cc b/auth/src/android/common_android.cc index 4499b7e0cf..0b8114c684 100644 --- a/auth/src/android/common_android.cc +++ b/auth/src/android/common_android.cc @@ -65,6 +65,7 @@ static const ErrorCodeMapping kCredentialCodes[] = { {"ERROR_MISSING_MULTI_FACTOR_INFO", kAuthErrorMissingMultiFactorInfo}, {"ERROR_INVALID_MULTI_FACTOR_SESSION", kAuthErrorInvalidMultiFactorSession}, {"ERROR_MULTI_FACTOR_INFO_NOT_FOUND", kAuthErrorMultiFactorInfoNotFound}, + {"ERROR_MISSING_OR_INVALID_NONCE", kAuthErrorMissingOrInvalidNonce}, {nullptr}, }; static const ErrorCodeMapping kUserCodes[] = { @@ -114,6 +115,7 @@ static const ErrorCodeMapping kFirebaseAuthCodes[] = { {"ERROR_UNSUPPORTED_FIRST_FACTOR", kAuthErrorUnsupportedFirstFactor}, {"ERROR_EMAIL_CHANGE_NEEDS_VERIFICATION", kAuthErrorEmailChangeNeedsVerification}, + {"ERROR_USER_CANCELLED", kAuthErrorMissingOrInvalidNonce}, {nullptr}, }; @@ -424,6 +426,25 @@ AuthError CheckAndClearJniAuthExceptions(JNIEnv* env, return kAuthErrorNone; } +// Checks for Future success and/or Android based Exceptions, and maps them +// to corresonding AuthError codes. +AuthError MapFutureCallbackResultToAuthError(JNIEnv* env, jobject result, + util::FutureResult result_code, + bool* success) { + *success = false; + switch (result_code) { + case util::kFutureResultSuccess: + *success = true; + return kAuthErrorNone; + case util::kFutureResultFailure: + return ErrorCodeFromException(env, result); + case util::kFutureResultCancelled: + return kAuthErrorCancelled; + default: + return kAuthErrorFailure; + } +} + // Convert j_user (a local reference to FirebaseUser) into a global reference, // delete the local reference, and update the user_impl pointer in auth_data // to refer to it, deleted the existing auth_data->user_impl reference if it diff --git a/auth/src/android/common_android.h b/auth/src/android/common_android.h index b6e0cdcaa4..da8c9f9aec 100644 --- a/auth/src/android/common_android.h +++ b/auth/src/android/common_android.h @@ -167,27 +167,52 @@ AuthError ErrorCodeFromException(JNIEnv* env, jobject exception); AuthError CheckAndClearJniAuthExceptions(JNIEnv* env, std::string* error_message); +// Checks for Future success / failure or Android based exceptions, and maps +// them to corresponding AuthError codes. +AuthError MapFutureCallbackResultToAuthError(JNIEnv* env, jobject result, + util::FutureResult result_code, + bool* success); + // The function called by the Java thread when a result completes. template void FutureCallback(JNIEnv* env, jobject result, util::FutureResult result_code, - int status, const char* status_message, - void* callback_data) { + const char* status_message, void* callback_data) { FutureCallbackData* data = static_cast*>(callback_data); + bool success = false; + const AuthError error = + MapFutureCallbackResultToAuthError(env, result, result_code, &success); + // Finish off the asynchronous call so that the caller can read it. + data->auth_data->future_impl.Complete( + data->handle, error, status_message, + [result, success, data](void* user_data) { + if (data->future_data_read_fn != nullptr) { + data->future_data_read_fn(result, data, success, user_data); + } + }); + + // Remove the callback structure that was allocated when the callback was + // created in SetupFuture(). + delete data; + data = nullptr; +} - AuthError error; +// The function called by the Java thread when a result completes. +template +void FederatedAuthProviderFutureCallback(JNIEnv* env, jobject result, + util::FutureResult result_code, + const char* status_message, + void* callback_data) { + FutureCallbackData* data = + static_cast*>(callback_data); bool success = false; - switch (result_code) { - case util::kFutureResultSuccess: - error = kAuthErrorNone; - success = true; - break; - case util::kFutureResultFailure: - error = ErrorCodeFromException(env, result); - break; - case util::kFutureResultCancelled: - error = kAuthErrorCancelled; - break; + AuthError error = + MapFutureCallbackResultToAuthError(env, result, result_code, &success); + // The Android SDK Web Activity returns Operation Not Allowed when the + // provider id is invalid or a federated auth operation is requested of a + // disabled provider. + if (error == kAuthErrorOperationNotAllowed) { + error = kAuthErrorInvalidProviderId; } // Finish off the asynchronous call so that the caller can read it. data->auth_data->future_impl.Complete( @@ -218,6 +243,23 @@ void RegisterCallback( auth_data->future_api_id.c_str()); } +// Akin to RegisterCallback above, but has a special callback handler +// to detect specific error codes associated with the phone's +// Web Activity implementation. This lets us map SDK-specific error idioms +// to consistent error codes for both iOS and Android without interfering +// with the existing API behavior for other sign in events. +template +void RegisterFederatedAuthProviderCallback( + jobject pending_result, SafeFutureHandle handle, AuthData* auth_data, + typename FutureCallbackData::ReadFutureResultFn read_result_fn) { + // The FutureCallbackData structure is deleted in + // FederatedAuthProviderFutureCallback(). + util::RegisterCallbackOnTask( + Env(auth_data), pending_result, FederatedAuthProviderFutureCallback, + new FutureCallbackData(handle, auth_data, read_result_fn), + auth_data->future_api_id.c_str()); +} + // Checks if there was an error, and if so, completes the given future with the // proper error message. Returns true if there was an error and the future was // completed. diff --git a/auth/src/android/credential_android.cc b/auth/src/android/credential_android.cc index 20f212d1c7..79ce2fbc8c 100644 --- a/auth/src/android/credential_android.cc +++ b/auth/src/android/credential_android.cc @@ -836,6 +836,11 @@ JNIEXPORT void JNICALL JniAuthPhoneListener::nativeOnCodeAutoRetrievalTimeOut( // FederatedAuthHandlers FederatedOAuthProvider::FederatedOAuthProvider() {} +FederatedOAuthProvider::FederatedOAuthProvider( + const FederatedOAuthProviderData& provider_data) { + provider_data_ = provider_data; +} + FederatedOAuthProvider::~FederatedOAuthProvider() {} void FederatedOAuthProvider::SetProviderData( @@ -922,7 +927,8 @@ Future FederatedOAuthProvider::SignIn(AuthData* auth_data) { auth_idp::GetMethodId(auth_idp::kStartActivityForSignInWithProvider), auth_data->app->activity(), oauthprovider); if (!CheckAndCompleteFutureOnError(env, &futures, handle)) { - RegisterCallback(task, handle, auth_data, ReadSignInResult); + RegisterFederatedAuthProviderCallback(task, handle, auth_data, + ReadSignInResult); } env->DeleteLocalRef(task); } @@ -945,7 +951,8 @@ Future FederatedOAuthProvider::Link(AuthData* auth_data) { user_idp::GetMethodId(user_idp::kStartActivityForLinkWithProvider), auth_data->app->activity(), oauthprovider); if (!CheckAndCompleteFutureOnError(env, &futures, handle)) { - RegisterCallback(task, handle, auth_data, ReadSignInResult); + RegisterFederatedAuthProviderCallback(task, handle, auth_data, + ReadSignInResult); } env->DeleteLocalRef(task); } @@ -970,7 +977,8 @@ Future FederatedOAuthProvider::Reauthenticate( user_idp::kStartActivityForReauthenticateWithProvider), auth_data->app->activity(), oauthprovider); if (!CheckAndCompleteFutureOnError(env, &futures, handle)) { - RegisterCallback(task, handle, auth_data, ReadSignInResult); + RegisterFederatedAuthProviderCallback(task, handle, auth_data, + ReadSignInResult); } env->DeleteLocalRef(task); } diff --git a/auth/src/desktop/auth_providers/federated_auth_provider.cc b/auth/src/desktop/auth_providers/federated_auth_provider.cc index d9c380dcbf..f9fe9555b7 100644 --- a/auth/src/desktop/auth_providers/federated_auth_provider.cc +++ b/auth/src/desktop/auth_providers/federated_auth_provider.cc @@ -27,6 +27,11 @@ namespace auth { FederatedOAuthProvider::FederatedOAuthProvider() { } +FederatedOAuthProvider::FederatedOAuthProvider( + const FederatedOAuthProviderData& provider_data) { + provider_data_ = provider_data; +} + FederatedOAuthProvider::~FederatedOAuthProvider() { handler_ = nullptr; } diff --git a/auth/src/include/firebase/auth.h b/auth/src/include/firebase/auth.h index 9de6bd5562..65e5064b4a 100644 --- a/auth/src/include/firebase/auth.h +++ b/auth/src/include/firebase/auth.h @@ -629,13 +629,13 @@ class IdTokenListener { #endif // not SWIG #ifdef INTERNAL_EXPERIMENTAL -#ifndef SWIG /// @brief Used to authenticate with Federated Auth Providers. /// /// The federated auth provider implementation may facilitate multiple provider /// types in the future, with support for OAuth to start. class FederatedAuthProvider { public: +#ifndef SWIG /// @brief Contains resulting information of a user authenticated by a /// Federated Auth Provider. This information will be used by the internal /// implementation to construct a corresponding User object. @@ -792,6 +792,7 @@ class FederatedAuthProvider { AuthError auth_error, const char* error_message); }; +#endif // not SWIG FederatedAuthProvider() { } virtual ~FederatedAuthProvider() {} @@ -815,6 +816,7 @@ class FederatedAuthProvider { /// account linking and user reauthentication, respectively. class FederatedOAuthProvider : public FederatedAuthProvider { public: +#ifndef SWIG /// @brief A FederatedAuthProvider typed specifically for OAuth Authentication /// handling. /// @@ -822,10 +824,20 @@ class FederatedOAuthProvider : public FederatedAuthProvider { /// UI flows. typedef FederatedAuthProvider::Handler AuthHandler; +#endif // !SWIG /// Constructs an unconfigured provider. FederatedOAuthProvider(); + /// Constructs a FederatedOAuthProvider preconfigured with provider data. + /// + /// @param[in] provider_data Contains the federated provider id and OAuth + /// scopes and OAuth custom parameters required for user authentication and + /// user linking. + explicit FederatedOAuthProvider( + const FederatedOAuthProviderData& provider_data); + +#ifndef SWIG /// @brief Constructs a provider with the required information to authenticate /// using an OAuth Provider. /// @@ -841,6 +853,7 @@ class FederatedOAuthProvider : public FederatedAuthProvider { /// to handle authentication requests. FederatedOAuthProvider(const FederatedOAuthProviderData& provider_data, AuthHandler* handler); +#endif // !SWIG ~FederatedOAuthProvider() override; @@ -850,6 +863,8 @@ class FederatedOAuthProvider : public FederatedAuthProvider { /// scopes and OAuth custom parameters required for user authentication and /// user linking. void SetProviderData(const FederatedOAuthProviderData& provider_data); + +#ifndef SWIG /// @brief Configures the use of an AuthHandler for non-mobile systems. /// /// The existence of a handler is required for non-mobile systems, and is @@ -860,6 +875,7 @@ class FederatedOAuthProvider : public FederatedAuthProvider { /// to handle authentication requests. The handler must outlive the instance /// of this FederatedOAuthProvider. void SetAuthHandler(AuthHandler* handler); +#endif // !SWIG private: friend class ::firebase::auth::Auth; @@ -871,7 +887,6 @@ class FederatedOAuthProvider : public FederatedAuthProvider { FederatedOAuthProviderData provider_data_; AuthHandler* handler_; }; -#endif // not SWIG #endif // INTERNAL_EXPERIMENTAL diff --git a/auth/src/include/firebase/auth/credential.h b/auth/src/include/firebase/auth/credential.h index 3f82abe535..ce24aae4b8 100644 --- a/auth/src/include/firebase/auth/credential.h +++ b/auth/src/include/firebase/auth/credential.h @@ -144,14 +144,8 @@ class FacebookAuthProvider { static Credential GetCredential(const char* access_token); #ifdef INTERNAL_EXPERIMENTAL - /// Return the providerId used for FederatedAuth. - /// - /// @return A string representation of the FacebookAuthProvider. - /// @see FederatedAuthProvider::SetProviderData - /// @see Auth::SignInWithProvider - /// @see User::ReauthenticateWithProvider - /// @see User::LinkWithProvider - static const char* GetProviderId() { return "facebook.com"; } + /// The provider id string used for FederatedAuth. + static constexpr const char* const kProviderId = "facebook.com"; #endif // INTERNAL_EXPERIMENTAL }; @@ -166,14 +160,8 @@ class GitHubAuthProvider { static Credential GetCredential(const char* token); #ifdef INTERNAL_EXPERIMENTAL - /// Return the providerId used for FederatedAuth. - /// - /// @return A string representation of the FacebookAuthProvider. - /// @see FederatedAuthProvider::SetProviderData - /// @see Auth::SignInWithProvider - /// @see User::ReauthenticateWithProvider - /// @see User::LinkWithProvider - static const char* GetProviderId() { return "github.com"; } + /// The provider id string used for FederatedAuth. + constexpr static const char* const kProviderId = "github.com"; #endif // INTERNAL_EXPERIMENTAL }; @@ -190,14 +178,8 @@ class GoogleAuthProvider { const char* access_token); #ifdef INTERNAL_EXPERIMENTAL - /// Return the providerId used for FederatedAuth. - /// - /// @return A string representation of the FacebookAuthProvider. - /// @see FederatedAuthProvider::SetProviderData - /// @see Auth::SignInWithProvider - /// @see User::ReauthenticateWithProvider - /// @see User::LinkWithProvider - static const char* GetProviderId() { return "google.com"; } + /// The provider id string used for FederatedAuth. + constexpr static const char* const kProviderId = "google.com"; #endif // INTERNAL_EXPERIMENTAL }; @@ -212,14 +194,8 @@ class PlayGamesAuthProvider { static Credential GetCredential(const char* server_auth_code); #ifdef INTERNAL_EXPERIMENTAL - /// Return the providerId used for FederatedAuth. - /// - /// @return A string representation of the FacebookAuthProvider. - /// @see FederatedAuthProvider::SetProviderData - /// @see Auth::SignInWithProvider - /// @see User::ReauthenticateWithProvider - /// @see User::LinkWithProvider - static const char* GetProviderId() { return "playgames.google.com"; } + /// The provider id string used for FederatedAuth. + constexpr static const char* const kProviderId = "playgames.google.com"; #endif // INTERNAL_EXPERIMENTAL }; @@ -235,17 +211,29 @@ class TwitterAuthProvider { static Credential GetCredential(const char* token, const char* secret); #ifdef INTERNAL_EXPERIMENTAL - /// Return the providerId used for FederatedAuth. - /// - /// @return A string representation of the FacebookAuthProvider. - /// @see FederatedAuthProvider::SetProviderData - /// @see Auth::SignInWithProvider - /// @see User::ReauthenticateWithProvider - /// @see User::LinkWithProvider - static const char* GetProviderId() { return "twitter.com"; } + /// The provider id string used for FederatedAuth. + constexpr static const char* const kProviderId = "twitter.com"; #endif // INTERNAL_EXPERIMENTAL }; + +#ifdef INTERNAL_EXPERIMENTAL +/// @brief Use an access token provided by Yahoo to authenticate. +class YahooAuthProvider { + public: + /// The provider id string used for FederatedAuth. + constexpr static const char* const kProviderId = "yahoo.com"; +}; + +/// @brief Use an access token provided by Microsoft to authenticate. +class MicrosoftAuthProvider { + public: + /// The provider id string used for FederatedAuth. + constexpr static const char* const kProviderId = "microsoft.com"; +}; +#endif // INTERNAL_EXPERIMENTAL + + /// @brief OAuth2.0+UserInfo auth provider (OIDC compliant and non-compliant). class OAuthProvider { public: diff --git a/auth/src/include/firebase/auth/types.h b/auth/src/include/firebase/auth/types.h index 07a791741e..1dfac918c8 100644 --- a/auth/src/include/firebase/auth/types.h +++ b/auth/src/include/firebase/auth/types.h @@ -411,23 +411,52 @@ enum AuthError { /// Indicates that an error occurred during a Federated Auth UI Flow when the /// user was prompted to enter their credentials. kAuthErrorFederatedSignInUserInteractionFailure, + + /// Indicates that a request was made with a missing or invalid nonce. + /// This can happen if the hash of the provided raw nonce did not match the + /// hashed nonce in the OIDC ID token payload. + kAuthErrorMissingOrInvalidNonce, + + /// Indicates that the user did not authorize the application during Generic + /// IDP sign-in. + kAuthErrorUserCancelled, + #endif // INTERNAL_EXEPERIMENTAL }; #ifdef INTERNAL_EXPERIMENTAL -// @brief Contains information to identify a Federated Auth Provider. +/// @brief Contains information required to authenticate with a third party +/// provider. struct FederatedProviderData { - // @brief The name of the provider against which authentication attempts will - // take place. + /// @brief contains the id of the provider to be used during sign-in, link, or + /// reauthentication requests. std::string provider_id; }; -// @brief Contains information to identify an OAuth povider. +/// @brief Contains information to identify an OAuth povider. struct FederatedOAuthProviderData : FederatedProviderData { - // OAuth parmeters which specify which rights of access are being requested. + /// Initailizes an empty provider data structure. + FederatedOAuthProviderData() {} + + /// Initializes the provider data structure with a provider id. + explicit FederatedOAuthProviderData(const std::string& provider) { + this->provider_id = provider; + } + + /// @brief Initializes the provider data structure with the specified provider + /// id, scopes and custom parameters. + FederatedOAuthProviderData( + const std::string& provider, std::vector scopes, + std::map custom_parameters) { + this->provider_id = provider; + this->scopes = scopes; + this->custom_parameters = custom_parameters; + } + + /// OAuth parmeters which specify which rights of access are being requested. std::vector scopes; - // OAuth parameters which are provided to the federated provider service. + /// OAuth parameters which are provided to the federated provider service. std::map custom_parameters; }; #endif // INTERNAL_EXPERIMENTAL diff --git a/auth/src/ios/auth_ios.mm b/auth/src/ios/auth_ios.mm index 6e646050f2..781361b09e 100644 --- a/auth/src/ios/auth_ios.mm +++ b/auth/src/ios/auth_ios.mm @@ -291,6 +291,16 @@ void SignInCallback(FIRUser *_Nullable user, NSError *_Nullable error, result); } +void SignInResultWithProviderCallback( + FIRAuthDataResult *_Nullable auth_result, NSError *_Nullable error, + SafeFutureHandle handle, AuthData *_Nonnull auth_data, + const FIROAuthProvider *_Nonnull ios_auth_provider /*unused */) { + // ios_auth_provider exists as a parameter to hold a reference to the FIROAuthProvider preventing + // its release by the Objective-C runtime during the asynchronous SignIn operation. + error = RemapBadProviderIDErrors(error); + SignInResultCallback(auth_result, error, handle, auth_data); +} + void SignInResultCallback(FIRAuthDataResult *_Nullable auth_result, NSError *_Nullable error, SafeFutureHandle handle, AuthData *auth_data) { User* user = AssignUser(auth_result.user, auth_data); @@ -426,6 +436,18 @@ void SignInResultCallback(FIRAuthDataResult *_Nullable auth_result, NSError *_Nu return MakeFuture(&futures, handle); } +// Remap iOS SDK errors reported by the UIDelegate. While these errors seem like +// user interaction errors, they are actually caused by bad provider ids. +NSError* RemapBadProviderIDErrors(NSError* _Nonnull error) { + if (error.code == FIRAuthErrorCodeWebSignInUserInteractionFailure && + [error.domain isEqualToString:@"FIRAuthErrorDomain"]) { + return [[NSError alloc] initWithDomain:error.domain + code:FIRAuthErrorCodeInvalidProviderID + userInfo:error.userInfo]; + } + return error; +} + // Not implemented for iOS. void EnableTokenAutoRefresh(AuthData *auth_data) {} void DisableTokenAutoRefresh(AuthData *auth_data) {} diff --git a/auth/src/ios/common_ios.h b/auth/src/ios/common_ios.h index 6b7f7afc8f..f75960f495 100644 --- a/auth/src/ios/common_ios.h +++ b/auth/src/ios/common_ios.h @@ -21,6 +21,7 @@ #import "FIRAuth.h" #import "FIRAuthCredential.h" +#import "FIROAuthProvider.h" #import "FIRUser.h" #import "FIRUserInfo.h" #import "FIRUserMetadata.h" @@ -100,6 +101,20 @@ void SignInResultCallback(FIRAuthDataResult *_Nullable auth_result, SafeFutureHandle handle, AuthData *_Nonnull auth_data); +/// Common code for all FederatedOAuth API calls which return a SignInResult and +/// must hold a reference to a FIROAuthProvider so that the provider is not +/// deallocated by the Objective-C environment. Directly invokes +/// SignInResultCallback(). +void SignInResultWithProviderCallback( + FIRAuthDataResult* _Nullable auth_result, NSError* _Nullable error, + SafeFutureHandle handle, AuthData *_Nonnull auth_data, + const FIROAuthProvider *_Nonnull ios_auth_provider); + +/// Remap iOS SDK errors reported by the UIDelegate. While these errors seem +/// like user interaction errors, they are actually caused by bad provider ids. +NSError* RemapBadProviderIDErrors(NSError* _Nonnull error); + + } // namespace auth } // namespace firebase diff --git a/auth/src/ios/credential_ios.mm b/auth/src/ios/credential_ios.mm index 98f2b8b9a2..df2b0e71b4 100644 --- a/auth/src/ios/credential_ios.mm +++ b/auth/src/ios/credential_ios.mm @@ -299,6 +299,10 @@ explicit PhoneAuthProviderData(FIRPhoneAuthProvider* objc_provider) // FederatedAuthHandlers FederatedOAuthProvider::FederatedOAuthProvider() { } +FederatedOAuthProvider::FederatedOAuthProvider(const FederatedOAuthProviderData& provider_data) { + provider_data_ = provider_data; +} + FederatedOAuthProvider::~FederatedOAuthProvider() { } void FederatedOAuthProvider::SetProviderData(const FederatedOAuthProviderData& provider_data) { @@ -312,9 +316,10 @@ void LinkWithProviderGetCredentialCallback(FIRAuthCredential* _Nullable credenti NSError* _Nullable error, SafeFutureHandle handle, AuthData* auth_data, - const FederatedOAuthProviderData& provider_data) { + const FIROAuthProvider* ios_auth_provider) { if (error && error.code != 0) { ReferenceCountedFutureImpl& futures = auth_data->future_impl; + error = RemapBadProviderIDErrors(error); futures.CompleteWithResult(handle, AuthErrorFromNSError(error), util::NSStringToString(error.localizedDescription).c_str(), SignInResult()); @@ -322,7 +327,8 @@ void LinkWithProviderGetCredentialCallback(FIRAuthCredential* _Nullable credenti [UserImpl(auth_data) linkWithCredential:credential completion:^(FIRAuthDataResult* _Nullable auth_result, NSError* _Nullable error) { - SignInResultCallback(auth_result, error, handle, auth_data); + SignInResultWithProviderCallback(auth_result, error, handle, auth_data, + ios_auth_provider); }]; } } @@ -330,21 +336,25 @@ void LinkWithProviderGetCredentialCallback(FIRAuthCredential* _Nullable credenti // Callback to funnel a result from a GetCredential request to reauthetnicateWithCredential request. // This fulfills User::ReauthenticateWithProvider functionality on iOS, which currently isn't // accessible via their current API. -void ReauthenticateWithProviderGetCredentialCallback( - FIRAuthCredential* _Nullable credential, NSError* _Nullable error, - SafeFutureHandle handle, AuthData* auth_data, - const FederatedOAuthProviderData& provider_data) { +void ReauthenticateWithProviderGetCredentialCallback(FIRAuthCredential* _Nullable credential, + NSError* _Nullable error, + SafeFutureHandle handle, + AuthData* auth_data, + const FIROAuthProvider* ios_auth_provider) { if (error && error.code != 0) { ReferenceCountedFutureImpl& futures = auth_data->future_impl; + error = RemapBadProviderIDErrors(error); futures.CompleteWithResult(handle, AuthErrorFromNSError(error), util::NSStringToString(error.localizedDescription).c_str(), SignInResult()); } else { [UserImpl(auth_data) reauthenticateWithCredential:credential - completion:^(FIRAuthDataResult* _Nullable auth_result, NSError* _Nullable error) { - SignInResultCallback(auth_result, error, handle, auth_data); - }]; + completion:^(FIRAuthDataResult* _Nullable auth_result, + NSError* _Nullable error) { + SignInResultWithProviderCallback(auth_result, error, handle, auth_data, + ios_auth_provider); + }]; } } @@ -363,7 +373,8 @@ void ReauthenticateWithProviderGetCredentialCallback( signInWithProvider:ios_provider UIDelegate:nullptr completion:^(FIRAuthDataResult* _Nullable auth_result, NSError* _Nullable error) { - SignInResultCallback(auth_result, error, handle, auth_data); + SignInResultWithProviderCallback(auth_result, error, handle, auth_data, + ios_provider); }]; return MakeFuture(&futures, handle); } else { @@ -392,7 +403,7 @@ void ReauthenticateWithProviderGetCredentialCallback( completion:^(FIRAuthCredential* _Nullable credential, NSError* _Nullable error) { LinkWithProviderGetCredentialCallback( - credential, error, handle, auth_data, provider_data_); + credential, error, handle, auth_data, ios_provider); }]; return MakeFuture(&futures, handle); } else { @@ -421,7 +432,7 @@ void ReauthenticateWithProviderGetCredentialCallback( completion:^(FIRAuthCredential* _Nullable credential, NSError* _Nullable error) { ReauthenticateWithProviderGetCredentialCallback( - credential, error, handle, auth_data, provider_data_); + credential, error, handle, auth_data, ios_provider); }]; return MakeFuture(&futures, handle); } else { diff --git a/build_tools/README.md b/build_tools/README.md new file mode 100644 index 0000000000..9b21c37a5e --- /dev/null +++ b/build_tools/README.md @@ -0,0 +1,4 @@ +# Build Tools + +This directory contains build configuration files used by kokoro to build +tooling needed for firebase releases. diff --git a/build_tools/release.cfg b/build_tools/release.cfg new file mode 100644 index 0000000000..9b5d59203f --- /dev/null +++ b/build_tools/release.cfg @@ -0,0 +1,13 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Location of the bash script to run. +build_file: "google3/firebase/app/client/cpp/build_tools/release.sh" + +gfile: "/x20/teams/firebase/build_tools/binutils-latest.tar.gz" + +action { + define_artifacts { + regex: "install-output/*" + strip_prefix: "install-output/" + } +} diff --git a/build_tools/release.sh b/build_tools/release.sh new file mode 100755 index 0000000000..b7fb2a74ae --- /dev/null +++ b/build_tools/release.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Builds build tools used in building Firebase Cpp Sdk + +# Fail on any error. +set -e +# Display commands being run. +set -x + +# Install binutils deps +sudo apt-get install -y texinfo bison flex + +# Extract latest binutil source from x20 +tar -xzf "${KOKORO_ARTIFACTS_DIR}/gfile/binutils-*.tar.gz" -C "${KOKORO_ARTIFACTS_DIR}/binutils" + +cd "${KOKORO_ARTIFACTS_DIR}/binutils" +./configure --enable-targets=all --prefix="${KOKORO_ARTIFACTS_DIR}/install-output" +make -j 8 all-binutils +make install diff --git a/cmake/download_pod_headers.cmake b/cmake/download_pod_headers.cmake index 5875c4f860..90dc697978 100644 --- a/cmake/download_pod_headers.cmake +++ b/cmake/download_pod_headers.cmake @@ -77,3 +77,33 @@ function(setup_pod_headers_target TARGET_NAME POD_DIR POD_LIST) ) endfunction() + +# Creates a symlink to the header files of the given framework. Used when +# include paths are expecting the header files to be in a subdirectory, when +# accessing the header files directly does not have them in the same structure. +# +# Args: +# LIBRARY_TARGET: The library to add the include directory to. +# DOWNLOAD_POD_TARGET: The target that downloads the pod files which will +# contain the framework. From the above function. +# FRAMEWORK_PATH: The full path to the framework. +# SYMLINK_NAME: The name of the symlink to use. Usually the framework name. +function(symlink_framework_headers LIBRARY_TARGET DOWNLOAD_POD_TARGET + FRAMEWORK_PATH SYMLINK_NAME) + # Guarantee the directory exists + set(HEADER_DIR "${PROJECT_BINARY_DIR}/FrameworkHeaders") + file(MAKE_DIRECTORY "${HEADER_DIR}") + + # Create a symlink to the headers + add_custom_command(TARGET ${DOWNLOAD_POD_TARGET} + POST_BUILD + COMMAND ln -sf ${FRAMEWORK_PATH}/Headers ${HEADER_DIR}/${SYMLINK_NAME} + COMMENT "Setting up the framework header directory for ${SYMLINK_NAME}." + ) + + # Add the symlink directory to the list of includes + target_include_directories(${LIBRARY_TARGET} + PRIVATE + ${HEADER_DIR} + ) +endfunction() diff --git a/cmake/external/libuv.cmake b/cmake/external/libuv.cmake index d90d4f7f13..3beb36d80b 100644 --- a/cmake/external/libuv.cmake +++ b/cmake/external/libuv.cmake @@ -22,7 +22,7 @@ ExternalProject_Add( libuv GIT_REPOSITORY https://github.com/libuv/libuv - GIT_TAG v1.23.2 + GIT_TAG v1.28.0 PREFIX ${PROJECT_BINARY_DIR} diff --git a/cmake/external/nanopb.cmake b/cmake/external/nanopb.cmake index 080039557c..40a3c4ccc8 100644 --- a/cmake/external/nanopb.cmake +++ b/cmake/external/nanopb.cmake @@ -22,8 +22,8 @@ ExternalProject_Add( nanopb DOWNLOAD_DIR ${FIREBASE_DOWNLOAD_DIR} - URL https://github.com/nanopb/nanopb/archive/nanopb-0.3.9.1.tar.gz - URL_HASH SHA256=67460d0c0ad331ef4d5369ad337056d0cd2f900c94887628d287eb56c69324bc + URL https://github.com/nanopb/nanopb/archive/0.3.9.2.tar.gz + URL_HASH SHA256=b8dd5cb0d184d424ddfea13ddee3f7b0920354334cbb44df434d55e5f0086b12 PREFIX ${PROJECT_BINARY_DIR} diff --git a/cmake/firebase_cpp_gradle.cmake b/cmake/firebase_cpp_gradle.cmake new file mode 100644 index 0000000000..fdae40b12b --- /dev/null +++ b/cmake/firebase_cpp_gradle.cmake @@ -0,0 +1,57 @@ +# Copyright 2019 Google +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# If the expected file does not already exist, defines a custom command to use +# the Gradle Wrapper in the root directory to invoke the given build rule. +# +# Args: +# GRADLE_TARGET: The gradle target that will be built, if needed. +# EXPECTED_FILE: The expected output from the gradle target. +function(firebase_cpp_gradle GRADLE_TARGET EXPECTED_FILE) + if(NOT EXISTS ${EXPECTED_FILE}) + if(WIN32) + set(gradle_command "gradlew.bat") + else() + set(gradle_command "./gradlew") + endif() + + # Run gradle to generated the expected file + add_custom_command( + COMMAND ${gradle_command} ${GRADLE_TARGET} + OUTPUT ${EXPECTED_FILE} + WORKING_DIRECTORY ${FIREBASE_SOURCE_DIR} + ) + endif() +endfunction() + +# Defines a variable for generating the proguard file for a given library. +# The variable is FIREBASE_CPP_${LIBRARY_NAME}_PROGUARD, all in upper case. +# +# Args: +# LIBRARY_NAME: The name of the library to define the proguard file for. +function(firebase_cpp_proguard_file LIBRARY_NAME) + string(TOUPPER "${LIBRARY_NAME}" upper_name) + set(proguard_var "FIREBASE_CPP_${upper_name}_PROGUARD") + set(${proguard_var} + "${FIREBASE_SOURCE_DIR}/${LIBRARY_NAME}/build/${LIBRARY_NAME}.pro" + CACHE FILEPATH "Proguard file for ${LIBRARY_NAME}" FORCE) + + firebase_cpp_gradle(":${LIBRARY_NAME}:externalNativeBuildRelease" + "${${proguard_var}}") + + add_custom_target( + "${LIBRARY_NAME}_cpp_proguard" + DEPENDS ${${proguard_var}} + ) +endfunction() \ No newline at end of file diff --git a/cpp_sdk_version.json b/cpp_sdk_version.json index 71208ff974..249bdc9405 100644 --- a/cpp_sdk_version.json +++ b/cpp_sdk_version.json @@ -1,5 +1,5 @@ { - "released": "6.6.0", - "stable": "6.6.0", - "head": "6.6.0" + "released": "6.7.0", + "stable": "6.7.0", + "head": "6.7.0" } diff --git a/database/CMakeLists.txt b/database/CMakeLists.txt index 4d27c21ce9..b201c18c9b 100644 --- a/database/CMakeLists.txt +++ b/database/CMakeLists.txt @@ -19,6 +19,7 @@ set (CMAKE_CXX_STANDARD 11) include(binary_to_array) include(download_pod_headers) +include(firebase_cpp_gradle) project(firebase_database NONE) enable_language(C) @@ -36,6 +37,8 @@ set(common_SRCS src/common/query.cc) # Define the resource build needed for Android +firebase_cpp_gradle(":database:database_resources:generateDexJarRelease" + "${CMAKE_CURRENT_LIST_DIR}/database_resources/build/dexed.jar") binary_to_array("database_resources" "${CMAKE_CURRENT_LIST_DIR}/database_resources/build/dexed.jar" "firebase_database_resources" @@ -194,7 +197,9 @@ else() add_definitions(-include assert.h -include string.h) endif() -if(IOS) +if(ANDROID) + firebase_cpp_proguard_file(database) +elseif(IOS) # Enable Automatic Reference Counting (ARC) set_property( TARGET firebase_database @@ -205,8 +210,8 @@ if(IOS) set(pod_target_name "download_database_pod_headers") set(pods_dir "${PROJECT_BINARY_DIR}/Pods") set(pod_list "") - list(APPEND pod_list "'Firebase/Core', '6.9.0'") - list(APPEND pod_list "'Firebase/Database', '6.9.0'") + list(APPEND pod_list "'Firebase/Core', '6.10.0'") + list(APPEND pod_list "'Firebase/Database', '6.10.0'") setup_pod_headers_target("${pod_target_name}" "${pods_dir}" "${pod_list}") diff --git a/database/database_resources/build.gradle b/database/database_resources/build.gradle index 97e66943c9..280a973b18 100644 --- a/database/database_resources/build.gradle +++ b/database/database_resources/build.gradle @@ -46,7 +46,7 @@ android { dependencies { implementation 'com.google.firebase:firebase-analytics:17.2.0' - implementation 'com.google.firebase:firebase-database:19.1.0' + implementation 'com.google.firebase:firebase-database:19.2.0' //implementation project(':app:app_resources') } diff --git a/database/src/android/database_reference_android.cc b/database/src/android/database_reference_android.cc index 99d4d67e7a..dafa606dc4 100644 --- a/database/src/android/database_reference_android.cc +++ b/database/src/android/database_reference_android.cc @@ -13,15 +13,16 @@ // limitations under the License. #include "database/src/android/database_reference_android.h" -#include "database/src/common/database_reference.h" #include #include + #include "app/src/reference_counted_future_impl.h" #include "app/src/util_android.h" #include "database/src/android/database_android.h" #include "database/src/android/disconnection_android.h" #include "database/src/android/util_android.h" +#include "database/src/common/database_reference.h" namespace firebase { namespace database { @@ -303,8 +304,8 @@ struct FutureCallbackData { }; void FutureCallback(JNIEnv* env, jobject result, util::FutureResult result_code, - int status, const char* status_message, - void* callback_data) { + const char* status_message, void* callback_data) { + int status = 0; // TODO(140207379): populate with proper status code FutureCallbackData* data = reinterpret_cast(callback_data); if (data != nullptr) { diff --git a/database/src/android/disconnection_android.cc b/database/src/android/disconnection_android.cc index 43a66c2fba..4ef5a75e6a 100644 --- a/database/src/android/disconnection_android.cc +++ b/database/src/android/disconnection_android.cc @@ -13,8 +13,10 @@ // limitations under the License. #include "database/src/android/disconnection_android.h" + #include #include + #include "app/src/future_manager.h" #include "app/src/util_android.h" #include "database/src/android/database_android.h" @@ -94,8 +96,8 @@ struct FutureCallbackData { }; void FutureCallback(JNIEnv* env, jobject result, util::FutureResult result_code, - int status, const char* status_message, - void* callback_data) { + const char* status_message, void* callback_data) { + int status = 0; // TODO(140207379): populate with proper status code FutureCallbackData* data = reinterpret_cast(callback_data); if (data != nullptr) { diff --git a/database/src/common/data_snapshot.cc b/database/src/common/data_snapshot.cc index facc7c4ac4..5515b6962a 100644 --- a/database/src/common/data_snapshot.cc +++ b/database/src/common/data_snapshot.cc @@ -14,6 +14,7 @@ #include "database/src/include/firebase/database/data_snapshot.h" +#include "app/src/assert.h" #include "app/src/include/firebase/internal/platform.h" #include "database/src/include/firebase/database.h" #include "database/src/include/firebase/database/database_reference.h" @@ -97,8 +98,8 @@ DataSnapshot::~DataSnapshot() { bool DataSnapshot::exists() const { return internal_ && internal_->Exists(); } DataSnapshot DataSnapshot::Child(const char* path) const { - return internal_ ? DataSnapshot(internal_->Child(path)) - : DataSnapshot(nullptr); + return internal_ && path ? DataSnapshot(internal_->Child(path)) + : DataSnapshot(nullptr); } DataSnapshot DataSnapshot::Child(const std::string& path) const { @@ -139,7 +140,7 @@ DatabaseReference DataSnapshot::GetReference() const { } bool DataSnapshot::HasChild(const char* path) const { - return internal_ && internal_->HasChild(path); + return internal_ && path && internal_->HasChild(path); } bool DataSnapshot::HasChild(const std::string& path) const { diff --git a/database/src/common/database.cc b/database/src/common/database.cc index 6bbcef6bc8..58a963d814 100644 --- a/database/src/common/database.cc +++ b/database/src/common/database.cc @@ -175,11 +175,13 @@ DatabaseReference Database::GetReference() const { } DatabaseReference Database::GetReference(const char* path) const { - return internal_ ? internal_->GetReference(path) : DatabaseReference(); + return internal_ && path ? internal_->GetReference(path) + : DatabaseReference(); } DatabaseReference Database::GetReferenceFromUrl(const char* url) const { - return internal_ ? internal_->GetReferenceFromUrl(url) : DatabaseReference(); + return internal_ && url ? internal_->GetReferenceFromUrl(url) + : DatabaseReference(); } void Database::GoOffline() { diff --git a/database/src/common/database_reference.cc b/database/src/common/database_reference.cc index a00cd67e9d..366097630b 100644 --- a/database/src/common/database_reference.cc +++ b/database/src/common/database_reference.cc @@ -14,6 +14,7 @@ #include "database/src/include/firebase/database/database_reference.h" +#include "app/src/assert.h" #include "app/src/include/firebase/internal/platform.h" #include "database/src/common/database_reference.h" @@ -166,8 +167,8 @@ DatabaseReference DatabaseReference::GetRoot() const { } DatabaseReference DatabaseReference::Child(const char* path) const { - return internal_ ? DatabaseReference(internal_->Child(path)) - : DatabaseReference(nullptr); + return internal_ && path ? DatabaseReference(internal_->Child(path)) + : DatabaseReference(nullptr); } DatabaseReference DatabaseReference::Child(const std::string& path) const { diff --git a/database/src/common/mutable_data.cc b/database/src/common/mutable_data.cc index 68c34bde44..0b1006d40a 100644 --- a/database/src/common/mutable_data.cc +++ b/database/src/common/mutable_data.cc @@ -14,6 +14,7 @@ #include "database/src/include/firebase/database/mutable_data.h" +#include "app/src/assert.h" #include "app/src/include/firebase/internal/platform.h" // QueryInternal is defined in these 3 files, one implementation for each OS. @@ -88,7 +89,8 @@ MutableData::~MutableData() { } MutableData MutableData::Child(const char* path) { - return internal_ ? MutableData(internal_->Child(path)) : MutableData(nullptr); + return internal_ && path ? MutableData(internal_->Child(path)) + : GetInvalidMutableData(); } MutableData MutableData::Child(const std::string& path) { @@ -120,10 +122,10 @@ Variant MutableData::priority() { } bool MutableData::HasChild(const char* path) const { - return internal_ ? internal_->HasChild(path) : false; + return internal_ && path ? internal_->HasChild(path) : false; } -bool MutableData::HasChild(const std::string& path) { +bool MutableData::HasChild(const std::string& path) const { return internal_ ? internal_->HasChild(path.c_str()) : false; } diff --git a/database/src/common/query.cc b/database/src/common/query.cc index b7b51226c2..b480328725 100644 --- a/database/src/common/query.cc +++ b/database/src/common/query.cc @@ -14,6 +14,7 @@ #include "database/src/common/query.h" +#include "app/src/assert.h" #include "app/src/include/firebase/internal/platform.h" #include "database/src/include/firebase/database.h" #include "database/src/include/firebase/database/query.h" @@ -120,10 +121,12 @@ Future Query::GetValueLastResult() { } void Query::AddValueListener(ValueListener* listener) { - if (internal_) internal_->AddValueListener(listener); + if (internal_ && listener) internal_->AddValueListener(listener); } void Query::RemoveValueListener(ValueListener* listener) { + // listener is allowed to be a nullptr. nullptr represents removing all + // listeners at this location. if (internal_) internal_->RemoveValueListener(listener); } @@ -132,10 +135,12 @@ void Query::RemoveAllValueListeners() { } void Query::AddChildListener(ChildListener* listener) { - if (internal_) internal_->AddChildListener(listener); + if (internal_ && listener) internal_->AddChildListener(listener); } void Query::RemoveChildListener(ChildListener* listener) { + // listener is allowed to be a nullptr. nullptr represents removing all + // listeners at this location. if (internal_) internal_->RemoveChildListener(listener); } @@ -153,7 +158,8 @@ void Query::SetKeepSynchronized(bool keep_sync) { } Query Query::OrderByChild(const char* path) { - return internal_ ? Query(internal_->OrderByChild(path)) : Query(nullptr); + return internal_ && path ? Query(internal_->OrderByChild(path)) + : Query(nullptr); } Query Query::OrderByChild(const std::string& path) { @@ -177,8 +183,9 @@ Query Query::StartAt(Variant order_value) { } Query Query::StartAt(Variant order_value, const char* child_key) { - return internal_ ? Query(internal_->StartAt(order_value, child_key)) - : Query(nullptr); + return internal_ && child_key + ? Query(internal_->StartAt(order_value, child_key)) + : Query(nullptr); } Query Query::EndAt(Variant order_value) { @@ -186,8 +193,9 @@ Query Query::EndAt(Variant order_value) { } Query Query::EndAt(Variant order_value, const char* child_key) { - return internal_ ? Query(internal_->EndAt(order_value, child_key)) - : Query(nullptr); + return internal_ && child_key + ? Query(internal_->EndAt(order_value, child_key)) + : Query(nullptr); } Query Query::EqualTo(Variant order_value) { @@ -195,8 +203,9 @@ Query Query::EqualTo(Variant order_value) { } Query Query::EqualTo(Variant order_value, const char* child_key) { - return internal_ ? Query(internal_->EqualTo(order_value, child_key)) - : Query(nullptr); + return internal_ && child_key + ? Query(internal_->EqualTo(order_value, child_key)) + : Query(nullptr); } Query Query::LimitToFirst(size_t limit) { diff --git a/database/src/desktop/core/cache_policy.cc b/database/src/desktop/core/cache_policy.cc new file mode 100644 index 0000000000..8c574e21fd --- /dev/null +++ b/database/src/desktop/core/cache_policy.cc @@ -0,0 +1,59 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "database/src/desktop/core/cache_policy.h" + +#include +#include + +namespace firebase { +namespace database { +namespace internal { + +const uint64_t kServerUpdatesBetweenCacheSizeChecks = 1000; + +const uint64_t kMaxNumberOfPrunableQueriesToKeep = 1000; + +// 20% at a time until we're below our max. +const double kPercentOfQueriesToPruneAtOnce = 0.2; + +CachePolicy::~CachePolicy() {} + +LRUCachePolicy::LRUCachePolicy(uint64_t max_size_bytes) + : max_size_bytes_(max_size_bytes) {} + +LRUCachePolicy::~LRUCachePolicy() {} + +bool LRUCachePolicy::ShouldPrune(uint64_t current_size_bytes, + uint64_t count_of_prunable_queries) const { + return current_size_bytes > max_size_bytes_ || + count_of_prunable_queries > kMaxNumberOfPrunableQueriesToKeep; +} + +bool LRUCachePolicy::ShouldCheckCacheSize( + uint64_t server_updates_since_last_check) const { + return server_updates_since_last_check > kServerUpdatesBetweenCacheSizeChecks; +} + +double LRUCachePolicy::GetPercentOfQueriesToPruneAtOnce() const { + return kPercentOfQueriesToPruneAtOnce; +} + +uint64_t LRUCachePolicy::GetMaxNumberOfQueriesToKeep() const { + return kMaxNumberOfPrunableQueriesToKeep; +} + +} // namespace internal +} // namespace database +} // namespace firebase diff --git a/database/src/desktop/core/cache_policy.h b/database/src/desktop/core/cache_policy.h new file mode 100644 index 0000000000..ca8dcefb02 --- /dev/null +++ b/database/src/desktop/core/cache_policy.h @@ -0,0 +1,63 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef FIREBASE_DATABASE_CLIENT_CPP_SRC_DESKTOP_CORE_CACHE_POLICY_H_ +#define FIREBASE_DATABASE_CLIENT_CPP_SRC_DESKTOP_CORE_CACHE_POLICY_H_ + +#include + +namespace firebase { +namespace database { +namespace internal { + +class CachePolicy { + public: + virtual ~CachePolicy(); + + virtual bool ShouldPrune(uint64_t current_size_bytes, + uint64_t count_of_prunable_queries) const = 0; + + virtual bool ShouldCheckCacheSize( + uint64_t serverUpdatesSinceLastCheck) const = 0; + + virtual double GetPercentOfQueriesToPruneAtOnce() const = 0; + + virtual uint64_t GetMaxNumberOfQueriesToKeep() const = 0; +}; + +class LRUCachePolicy : public CachePolicy { + public: + explicit LRUCachePolicy(uint64_t max_size_bytes); + + ~LRUCachePolicy() override; + + bool ShouldPrune(uint64_t current_size_bytes, + uint64_t count_of_prunable_queries) const override; + + bool ShouldCheckCacheSize( + uint64_t server_updates_since_last_check) const override; + + double GetPercentOfQueriesToPruneAtOnce() const override; + + uint64_t GetMaxNumberOfQueriesToKeep() const override; + + private: + const uint64_t max_size_bytes_; +}; + +} // namespace internal +} // namespace database +} // namespace firebase + +#endif // FIREBASE_DATABASE_CLIENT_CPP_SRC_DESKTOP_CORE_CACHE_POLICY_H_ diff --git a/database/src/desktop/core/compound_write.cc b/database/src/desktop/core/compound_write.cc index f5ad9fd673..4489128a75 100644 --- a/database/src/desktop/core/compound_write.cc +++ b/database/src/desktop/core/compound_write.cc @@ -60,8 +60,15 @@ CompoundWrite CompoundWrite::EmptyWrite() { return CompoundWrite(); } CompoundWrite CompoundWrite::AddWrite(const Path& path, const Optional& variant) const { + CompoundWrite target = *this; + target.AddWriteInline(path, variant); + return target; +} + +void CompoundWrite::AddWriteInline(const Path& path, + const Optional& variant) { if (path.empty()) { - return CompoundWrite(Tree(variant)); + *this = CompoundWrite(Tree(variant)); } else { Optional root_most_path = write_tree_.FindRootMostPathWithValue(path); if (root_most_path.has_value()) { @@ -78,18 +85,13 @@ CompoundWrite CompoundWrite::AddWrite(const Path& path, if (!relative_path->empty() && IsPriorityKey(back) && VariantIsEmpty(VariantGetChild(value, relative_path->GetParent()))) { // Ignore priority updates on empty variants - return *this; } else { - CompoundWrite result = *this; Variant updated_variant = *value; VariantUpdateChild(&updated_variant, *relative_path, *variant); - result.write_tree_.SetValueAt(*root_most_path, updated_variant); - return result; + write_tree_.SetValueAt(*root_most_path, updated_variant); } } else { - CompoundWrite result = *this; - result.write_tree_.SetValueAt(path, variant); - return result; + write_tree_.SetValueAt(path, variant); } } } @@ -109,6 +111,20 @@ CompoundWrite CompoundWrite::AddWrite(const std::string& key, return AddWrite(Path(key), Optional(value)); } +void CompoundWrite::AddWriteInline(const Path& path, const Variant& value) { + AddWriteInline(path, Optional(value)); +} + +void CompoundWrite::AddWriteInline(const std::string& key, + const Optional& value) { + AddWriteInline(Path(key), value); +} + +void CompoundWrite::AddWriteInline(const std::string& key, + const Variant& value) { + AddWriteInline(Path(key), Optional(value)); +} + CompoundWrite CompoundWrite::AddWrites(const Path& path, const CompoundWrite& updates) const { return updates.write_tree_.Fold( @@ -118,6 +134,15 @@ CompoundWrite CompoundWrite::AddWrites(const Path& path, }); } +void CompoundWrite::AddWritesInline(const Path& path, + const CompoundWrite& updates) { + updates.write_tree_.Fold( + 0, [&path, this](Path relative_path, Variant value, int) { + AddWriteInline(path.GetChild(relative_path), Optional(value)); + return 0; + }); +} + CompoundWrite CompoundWrite::RemoveWrite(const Path& path) const { if (path.empty()) { return CompoundWrite(); @@ -133,6 +158,18 @@ CompoundWrite CompoundWrite::RemoveWrite(const Path& path) const { return CompoundWrite(); } +void CompoundWrite::RemoveWriteInline(const Path& path) { + if (path.empty()) { + *this = CompoundWrite(); + } else { + Tree* subtree = write_tree_.GetChild(path); + if (subtree) { + subtree->children().clear(); + subtree->value().reset(); + } + } +} + bool CompoundWrite::HasCompleteWrite(const Path& path) const { return GetCompleteVariant(path).has_value(); } diff --git a/database/src/desktop/core/compound_write.h b/database/src/desktop/core/compound_write.h index 3c22ad3826..2642e3138d 100644 --- a/database/src/desktop/core/compound_write.h +++ b/database/src/desktop/core/compound_write.h @@ -66,10 +66,20 @@ class CompoundWrite { const Optional& value) const; CompoundWrite AddWrite(const std::string& key, const Variant& value) const; + // Incorperate the new value to write at the given path. + void AddWriteInline(const Path& path, const Optional& variant); + void AddWriteInline(const Path& path, const Variant& value); + void AddWriteInline(const std::string& key, + const Optional& value); + void AddWriteInline(const std::string& key, const Variant& value); + // Create a new CompoundWrite that incorperates all of the writes in the given // CompoundWrite at the given path. CompoundWrite AddWrites(const Path& path, const CompoundWrite& updates) const; + // Incorperate all of the writes in the given CompoundWrite at the given path. + void AddWritesInline(const Path& path, const CompoundWrite& updates); + // Will remove a write at the given path and deeper paths. This will not // modify a write at a higher location, which must be removed by calling this // method with that path. @@ -77,6 +87,11 @@ class CompoundWrite { // Returns the new WriteCompound with the removed path CompoundWrite RemoveWrite(const Path& path) const; + // Will remove a write at the given path and deeper paths. This will not + // modify a write at a higher location, which must be removed by calling this + // method with that path. + void RemoveWriteInline(const Path& path); + // Returns whether this CompoundWrite will fully overwrite a node at a given // location and can therefore be considered "complete". bool HasCompleteWrite(const Path& path) const; diff --git a/database/src/desktop/core/repo.cc b/database/src/desktop/core/repo.cc index bc78c38b29..e8741d7dad 100644 --- a/database/src/desktop/core/repo.cc +++ b/database/src/desktop/core/repo.cc @@ -373,8 +373,9 @@ void Repo::UpdateChildren(const Path& path, const Variant& data, } // Start with our existing data and merge each child into it. - // std::map serverValues = - const CompoundWrite& resolved = updates; + Variant server_values = GenerateServerValues(server_time_offset_); + CompoundWrite resolved = + ResolveDeferredValueMerge(updates, server_values); WriteId write_id = GetNextWriteId(); std::vector events = server_sync_tree_->ApplyUserMerge( diff --git a/database/src/desktop/core/sync_tree.cc b/database/src/desktop/core/sync_tree.cc index 9812081abe..4577c5ce47 100644 --- a/database/src/desktop/core/sync_tree.cc +++ b/database/src/desktop/core/sync_tree.cc @@ -53,8 +53,8 @@ std::vector SyncTree::AckUserWrite(WriteId write_id, AckStatus revert, if (persist) { this->persistence_manager_->RemoveUserWrite(write_id); } - // Need to move the write out, as it is about to be deleted. - UserWriteRecord write = std::move(*pending_write_tree_->GetWrite(write_id)); + // Make a copy of the write, as it is about to be deleted. + UserWriteRecord write = *pending_write_tree_->GetWrite(write_id); bool need_to_reevaluate = pending_write_tree_->RemoveWrite(write_id); if (write.visible) { if (!revert) { diff --git a/database/src/desktop/core/tree.h b/database/src/desktop/core/tree.h index c1f250fd5e..438ca5b131 100644 --- a/database/src/desktop/core/tree.h +++ b/database/src/desktop/core/tree.h @@ -11,12 +11,12 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. - #ifndef FIREBASE_DATABASE_CLIENT_CPP_SRC_DESKTOP_CORE_TREE_H_ #define FIREBASE_DATABASE_CLIENT_CPP_SRC_DESKTOP_CORE_TREE_H_ #include #include + #include "app/src/optional.h" #include "app/src/path.h" #include "database/src/desktop/util_desktop.h" @@ -151,6 +151,137 @@ class Tree { return SetValueAt(path, Optional(std::move(value))); } + // Returns the root-most element in the tree in the given path. For example, + // if a Tree contains the following values: + // + // -+ : None + // | + // +- "Foo": None + // | | + // | +- "Bar": 100 + // | | + // | +- "Baz": 200 + // | + // +- "quux": 300 + // + // And the path given is "foo/bar/baz", then the return value will be a + // pointer to 100, because that is the root-most value in the given path. If + // a value cannot be found then nullptr is returned. + const Value* RootMostValue(const Path& path) const { + return RootMostValueMatching(path, [](const Value& value) { return true; }); + } + + // Returns the root-most element in the tree in the given path that matches + // the given predicate. For example, if a Tree contains the following + // values: + // + // -+ : 0 + // | + // +- "Foo": 50 + // | | + // | +- "Bar": 100 + // | | + // | +- "Baz": 200 + // | + // +- "quux": 300 + // + // And the path given is "foo/bar/baz", and the predicate is value > 75, then + // the return value will be a pointer to 100, because that is the root-most + // value in the given path that meets the condition given. + template + const Value* RootMostValueMatching(const Path& path, + const Func& predicate) const { + if (value_.has_value() && predicate(*value_)) { + return &value_.value(); + } else { + const Tree* current_tree = this; + for (const std::string& directory : path.GetDirectories()) { + current_tree = current_tree->GetChild(directory); + if (current_tree == nullptr) { + return nullptr; + } else if (current_tree->value_.has_value() && + predicate(*current_tree->value_)) { + return ¤t_tree->value_.value(); + } + } + return nullptr; + } + } + + // Returns the leaf-most element in the tree in the given path. For example, + // if a Tree contains the following values: + // + // -+ : None + // | + // +- "Foo": 200 + // | | + // | +- "Bar": 100 + // | | + // | +- "Baz": None + // | + // +- "quux": 300 + // + // And the path given is "foo/bar/baz", then the return value will be a + // pointer to 100, because that is the leaf-most value in the given path. If + // a value cannot be found then nullptr is returned. + const Value* LeafMostValue(const Path& path) const { + return LeafMostValueMatching(path, [](const Value& value) { return true; }); + } + + // Returns the leaf-most element in the tree in the given path that matches + // the given predicate. For example, if a Tree contains the following + // values: + // + // -+ : 200 + // | + // +- "Foo": 100 + // | | + // | +- "Bar": 50 + // | | + // | +- "Baz": 0 + // | + // +- "quux": 300 + // + // And the path given is "foo/bar/baz", and the predicate is value > 75, then + // the return value will be a pointer to 100, because that is the leaf-most + // value in the given path that meets the condition given. + template + const Value* LeafMostValueMatching(const Path& path, + const Func& predicate) const { + const Value* current_value = + (value_.has_value() && predicate(*value_)) ? &value_.value() : nullptr; + const Tree* current_tree = this; + for (const std::string& directory : path.GetDirectories()) { + current_tree = current_tree->GetChild(directory); + if (current_tree == nullptr) { + return current_value; + } else { + if (current_tree->value_.has_value() && + predicate(*current_tree->value_)) { + current_value = ¤t_tree->value_.value(); + } + } + } + return current_value; + } + + // Returns true if any location at or beneath this location in the tree meets + // the criteria given by the predicate. + template + bool ContainsMatchingValue(const Func& predicate) const { + if (value_.has_value() && predicate(*value_)) { + return true; + } else { + for (auto& key_subtree_pair : children_) { + const Tree& subtree = key_subtree_pair.second; + if (subtree.ContainsMatchingValue(predicate)) { + return true; + } + } + return false; + } + } + // Get a child node using the given key. Tree* GetChild(const std::string& key) { if (key.empty()) { diff --git a/database/src/desktop/core/write_tree.cc b/database/src/desktop/core/write_tree.cc index c922f47dc1..965e8c172d 100644 --- a/database/src/desktop/core/write_tree.cc +++ b/database/src/desktop/core/write_tree.cc @@ -40,7 +40,7 @@ void WriteTree::AddOverwrite(const Path& path, const Variant& snap, all_writes_.push_back( UserWriteRecord(write_id, path, snap, visibility == kOverwriteVisible)); if (visibility == kOverwriteVisible) { - visible_writes_ = visible_writes_.AddWrite(path, snap); + visible_writes_.AddWriteInline(path, snap); } last_write_id_ = write_id; } @@ -51,7 +51,7 @@ void WriteTree::AddMerge(const Path& path, // Stacking an older write on top of newer ones. FIREBASE_DEV_ASSERT(write_id > last_write_id_); all_writes_.push_back(UserWriteRecord(write_id, path, changed_children)); - visible_writes_ = visible_writes_.AddWrites(path, changed_children); + visible_writes_.AddWritesInline(path, changed_children); last_write_id_ = write_id; } @@ -116,12 +116,11 @@ bool WriteTree::RemoveWrite(WriteId write_id) { // There's no shadowing. We can safely just remove the write(s) from // visible_writes_. if (write_to_remove.is_overwrite) { - visible_writes_ = visible_writes_.RemoveWrite(write_to_remove.path); + visible_writes_.RemoveWriteInline(write_to_remove.path); } else { for (auto& entry : write_to_remove.merge.write_tree().children()) { Path path(entry.first); - visible_writes_ = - visible_writes_.RemoveWrite(write_to_remove.path.GetChild(path)); + visible_writes_.RemoveWriteInline(write_to_remove.path.GetChild(path)); } } return true; diff --git a/database/src/desktop/persistence/prune_forest.cc b/database/src/desktop/persistence/prune_forest.cc new file mode 100644 index 0000000000..4986dc9584 --- /dev/null +++ b/database/src/desktop/persistence/prune_forest.cc @@ -0,0 +1,131 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "database/src/desktop/persistence/prune_forest.h" + +#include +#include + +#include "app/src/assert.h" +#include "app/src/path.h" +#include "database/src/desktop/core/tree.h" + +namespace firebase { +namespace database { +namespace internal { + +static const Tree kPruneTree(true); // NOLINT +static const Tree kKeepTree(false); // NOLINT + +static bool KeepPredicate(bool prune) { return !prune; } +static bool PrunePredicate(bool prune) { return prune; } + +PruneForestRef::PruneForestRef(PruneForest* prune_forest) + : prune_forest_(prune_forest) {} + +bool PruneForestRef::operator==(const PruneForestRef& other) const { + return (prune_forest_ == other.prune_forest_) || + (prune_forest_ != nullptr && other.prune_forest_ != nullptr && + *prune_forest_ == *other.prune_forest_); +} + +bool PruneForestRef::PrunesAnything() const { + return prune_forest_->ContainsMatchingValue(PrunePredicate); +} + +bool PruneForestRef::ShouldPruneUnkeptDescendants(const Path& path) const { + const bool* should_prune = prune_forest_->LeafMostValue(path); + return should_prune != nullptr && *should_prune; +} + +bool PruneForestRef::ShouldKeep(const Path& path) const { + const bool* should_prune = prune_forest_->LeafMostValue(path); + return should_prune != nullptr && !*should_prune; +} + +bool PruneForestRef::AffectsPath(const Path& path) const { + return prune_forest_->RootMostValue(path) != nullptr || + (prune_forest_->GetChild(path) && + !prune_forest_->GetChild(path)->IsEmpty()); +} + +PruneForestRef PruneForestRef::GetChild(const std::string& key) { + return PruneForestRef(prune_forest_->GetOrMakeSubtree(Path(key))); +} + +const PruneForestRef PruneForestRef::GetChild(const std::string& key) const { + return PruneForestRef(prune_forest_->GetOrMakeSubtree(Path(key))); +} + +PruneForestRef PruneForestRef::GetChild(const Path& path) { + return PruneForestRef(prune_forest_->GetOrMakeSubtree(path)); +} + +const PruneForestRef PruneForestRef::GetChild(const Path& path) const { + return PruneForestRef(prune_forest_->GetOrMakeSubtree(path)); +} + +void PruneForestRef::Prune(const Path& path) { + FIREBASE_DEV_ASSERT_MESSAGE( + prune_forest_->RootMostValueMatching(path, KeepPredicate) == nullptr, + "Can't prune path that was kept previously!") + if (prune_forest_->RootMostValueMatching(path, PrunePredicate) != nullptr) { + // This path will already be pruned + } else { + *prune_forest_->GetOrMakeSubtree(path) = kPruneTree; + } +} + +void PruneForestRef::Keep(const Path& path) { + if (prune_forest_->RootMostValueMatching(path, KeepPredicate) != nullptr) { + // This path will already be kept + } else { + *prune_forest_->GetOrMakeSubtree(path) = kKeepTree; + } +} + +void PruneForestRef::KeepAll(const Path& path, + const std::set& children) { + if (prune_forest_->RootMostValueMatching(path, KeepPredicate) != nullptr) { + // This path will already be kept. + } else { + DoAll(path, children, kKeepTree); + } +} + +void PruneForestRef::PruneAll(const Path& path, + const std::set& children) { + FIREBASE_DEV_ASSERT_MESSAGE( + prune_forest_->RootMostValueMatching(path, KeepPredicate) == nullptr, + "Can't prune path that was kept previously!"); + + if (prune_forest_->RootMostValueMatching(path, PrunePredicate) != nullptr) { + // This path will already be pruned. + } else { + DoAll(path, children, kPruneTree); + } +} + +void PruneForestRef::DoAll(const Path& path, + const std::set& children, + const Tree& keep_or_prune_tree) { + Tree* subtree = prune_forest_->GetChild(path); + for (const std::string& directory : children) { + *subtree->GetOrMakeSubtree(Path(directory)) = keep_or_prune_tree; + } +} + +} // namespace internal +} // namespace database +} // namespace firebase diff --git a/database/src/desktop/persistence/prune_forest.h b/database/src/desktop/persistence/prune_forest.h new file mode 100644 index 0000000000..b6b9f3198b --- /dev/null +++ b/database/src/desktop/persistence/prune_forest.h @@ -0,0 +1,129 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef FIREBASE_DATABASE_CLIENT_CPP_SRC_DESKTOP_PERSISTENCE_PRUNE_FOREST_H_ +#define FIREBASE_DATABASE_CLIENT_CPP_SRC_DESKTOP_PERSISTENCE_PRUNE_FOREST_H_ + +#include +#include + +#include "app/src/path.h" +#include "database/src/desktop/core/tree.h" + +namespace firebase { +namespace database { +namespace internal { + +// Forest of "prune trees" where a prune tree is a location that can be pruned +// with a tree of descendants that must be excluded from the pruning. +// +// Internally we store this as a single tree of bools with the following +// characteristics: +// +// * 'true' indicates a location that can be pruned, possibly +// with some excluded descendants. +// * 'false' indicates a location that we should keep (i.e. exclude from +// pruning). +// * 'true' (prune) cannot be a descendant of 'false' (keep). This will +// trigger an exception. +// * 'true' cannot be a descendant of 'true' (we'll just keep the more shallow +// 'true'). +// * 'false' cannot be a descendant of 'false' (we'll just keep the more +// shallow 'false'). +// +typedef Tree PruneForest; + +// A PruneTreeRef is a way to operate on a PruneTree, treating any node as the +// root. It provides fucntions to set or keep various locations, as well as +// GetChild allow you to drill into the children of a location in the tree. +// PruneForestRef is a lightweight object that does not take ownership of the +// PruneForest passed to it. +class PruneForestRef { + public: + explicit PruneForestRef(PruneForest* prune_forest); + + // Check if this PruneForestRef is equal to another PruneForestRef. This is + // mostly used for testing. + bool operator==(const PruneForestRef& other) const; + + // Check if this PruneForestRef is not equal to another PruneForestRef. This + // is mostly used for testing. + bool operator!=(const PruneForestRef& other) const { + return !(*this == other); + } + + // Returns true if this PruneForestRef prunes anything. + bool PrunesAnything() const; + + // Indicates that path is marked for pruning, so anything below it that didn't + // have keep() called on it should be pruned. + // + // @param path The path in question + // @return True if we should prune descendants that didn't have keep() called + // on them. + bool ShouldPruneUnkeptDescendants(const Path& path) const; + + // Returns true if the given path should be kept. + bool ShouldKeep(const Path& path) const; + + // Returns true if the given path should be pruned. + bool AffectsPath(const Path& path) const; + + // Get the child of this tree at the given key. + PruneForestRef GetChild(const std::string& key); + const PruneForestRef GetChild(const std::string& key) const; + + // Get the child of this tree at the given path. + PruneForestRef GetChild(const Path& path); + const PruneForestRef GetChild(const Path& path) const; + + // Mark that the value at the given path should be pruned. + void Prune(const Path& path); + + // Mark that the value at the given path should be kept. + void Keep(const Path& path); + + // Mark that the given child values at the given path should be kept. + void KeepAll(const Path& path, const std::set& children); + + // Mark that the given child values at the given path should be pruned. + void PruneAll(const Path& path, const std::set& children); + + // Accumulate a result given a visitor function to be applied to all nodes + // that are marked as being kept. + template + T FoldKeptNodes(const T& start_value, const Func& visitor) const { + return prune_forest_->Fold( + start_value, + [visitor](const Path& relative_path, bool prune, const T& accum) -> T { + if (!prune) { + return visitor(relative_path, nullptr, accum); + } else { + return accum; + } + }); + } + + private: + void DoAll(const Path& path, const std::set& children, + const Tree& keep_or_prune_tree); + + PruneForest* prune_forest_; +}; + +} // namespace internal +} // namespace database +} // namespace firebase + +#endif // FIREBASE_DATABASE_CLIENT_CPP_SRC_DESKTOP_PERSISTENCE_PRUNE_FOREST_H_ diff --git a/database/src/desktop/query_params_comparator.cc b/database/src/desktop/query_params_comparator.cc index bf52c19a86..fae3802635 100644 --- a/database/src/desktop/query_params_comparator.cc +++ b/database/src/desktop/query_params_comparator.cc @@ -15,6 +15,7 @@ #include "database/src/desktop/query_params_comparator.h" #include +#include #include "app/src/util.h" #include "database/src/desktop/util_desktop.h" @@ -30,14 +31,14 @@ const char QueryParamsComparator::kMaxKey[] = "[MAX_KEY]"; // down values with these types. These are never compared to other variants // directly - they should only be used with the QueryParamsComparator, which // uses the Variant::type() to do comparisons. -static const Variant kMinVariant = Variant::FromStaticBlob( +static const Variant kMinVariant = Variant::FromStaticBlob( // NOLINT QueryParamsComparator::kMinKey, sizeof(QueryParamsComparator::kMinKey)); -static const Variant kMaxVariant = Variant::FromStaticBlob( +static const Variant kMaxVariant = Variant::FromStaticBlob( // NOLINT QueryParamsComparator::kMaxKey, sizeof(QueryParamsComparator::kMaxKey)); -const std::pair QueryParamsComparator::kMinNode = +const std::pair QueryParamsComparator::kMinNode = // NOLINT std::make_pair(QueryParamsComparator::kMinKey, kMinVariant); -const std::pair QueryParamsComparator::kMaxNode = +const std::pair QueryParamsComparator::kMaxNode = // NOLINT std::make_pair(QueryParamsComparator::kMaxKey, kMaxVariant); static int VariantIsSentinel(const Variant& key, const Variant& value) { @@ -165,6 +166,7 @@ int QueryParamsComparator::CompareValues(const Variant& variant_a, kPrecedenceSentinel, // kTypeStaticBlob kPrecedenceError, // kTypeMutableBlob }; + Variant::Type type_a = value_a->type(); Variant::Type type_b = value_b->type(); Precedence precedence_a = kPrecedenceLookupTable[type_a]; @@ -211,7 +213,12 @@ int QueryParamsComparator::CompareValues(const Variant& variant_a, case kPrecedenceNumber: { // If they're both integers. if (type_a == Variant::kTypeInt64 && type_b == Variant::kTypeInt64) { - return value_a->int64_value() - value_b->int64_value(); + int64_t int64_a = value_a->int64_value(); + int64_t int64_b = value_b->int64_value(); + + if (int64_a < int64_b) return -1; + if (int64_a > int64_b) return 1; + return 0; } // At least one of them is a double, so we treat them both as doubles. @@ -226,8 +233,10 @@ int QueryParamsComparator::CompareValues(const Variant& variant_a, double double_b = (type_b == Variant::kTypeDouble) ? value_b->double_value() : static_cast(value_b->int64_value()); - double result = double_a - double_b; - return (result == 0.0) ? 0 : (result > 0.0 ? 1 : -1); + + if (double_a < double_b) return -1; + if (double_a > double_b) return 1; + return 0; } case kPrecedenceString: { return strcmp(value_a->string_value(), value_b->string_value()); diff --git a/database/src/desktop/util_desktop.cc b/database/src/desktop/util_desktop.cc index 327c697de2..f615d443e9 100644 --- a/database/src/desktop/util_desktop.cc +++ b/database/src/desktop/util_desktop.cc @@ -45,7 +45,7 @@ namespace firebase { namespace database { namespace internal { -const Variant kNullVariant = Variant::Null(); +const Variant kNullVariant = Variant::Null(); // NOLINT const char kValueKey[] = ".value"; const char kPriorityKey[] = ".priority"; @@ -434,17 +434,13 @@ bool HasVector(const Variant& variant) { return false; } -bool ParseInteger(const char* str, int32_t* output) { +bool ParseInteger(const char* str, int64_t* output) { assert(output); assert(str); - // Integers must not have leading zeroes. - if (str[0] == '0' && str[1] != '\0') { - return false; - } // Check if the key is numeric bool is_int = false; char* end_ptr = nullptr; - int32_t parse_value = strtol(str, &end_ptr, 10); // NOLINT + int64_t parse_value = strtoll(str, &end_ptr, 10); // NOLINT if (end_ptr != nullptr && end_ptr != str && *end_ptr == '\0') { is_int = true; *output = parse_value; @@ -452,43 +448,56 @@ bool ParseInteger(const char* str, int32_t* output) { return is_int; } -// Convert one level of map to vector if applicable. -// Convert map to vector if: +// A Variant map can be converted into a Variant vector if: // 1. map is not empty and -// 2. All the key are numeric and +// 2. All the key are integer (no leading 0) and // 3. If less or equal to half of the keys in the array is missing. +// Return whether the map can be converted into a vector, and output +// max_index_out as the highest numeric key found in the map. +bool CanConvertVariantMapToVector(const Variant& variant, + int64_t* max_index_out) { + if (!variant.is_map() || variant.map().empty()) return false; + + int64_t max_index = -1; + for (auto& it_child : variant.map()) { + assert(it_child.first.is_string()); + // Integers must not have leading zeroes. + if (it_child.first.string_value()[0] == '0' && + it_child.first.string_value()[1] != '\0') { + return false; + } + // Check if the key is numeric + int64_t parse_value = 0; + bool is_number = ParseInteger(it_child.first.string_value(), &parse_value); + if (!is_number || parse_value < 0) { + // If any one of the key is not numeric, there is no need to verify + // other keys + return false; + } + max_index = max_index < parse_value ? parse_value : max_index; + } + + if (max_index_out) *max_index_out = max_index; + return max_index < (2 * variant.map().size()); +} + +// Convert one level of map to vector if applicable. // This function assume no priority information remains in the variant. void ConvertMapToVector(Variant* variant) { assert(variant); - if (variant->is_map() && !variant->map().empty()) { - int64_t max_index = -1; - for (auto& it_child : variant->map()) { - // Check if the key is numeric - int32_t parse_value = 0; - bool is_number = - ParseInteger(it_child.first.string_value(), &parse_value); - if (!is_number || parse_value < 0) { - // If any one of the key is not numeric, there is no need to verify - // other keys - return; + int64_t max_index = -1; + if (CanConvertVariantMapToVector(*variant, &max_index)) { + Variant array_result(std::vector(max_index + 1, Variant::Null())); + for (int i = 0; i <= max_index; ++i) { + std::stringstream ss; + ss << i; + auto it_child = variant->map().find(ss.str()); + if (it_child != variant->map().end()) { + array_result.vector()[i] = it_child->second; } - max_index = max_index < parse_value ? parse_value : max_index; - } - - if (max_index < (2 * variant->map().size())) { - Variant array_result( - std::vector(max_index + 1, Variant::Null())); - for (int i = 0; i <= max_index; ++i) { - std::stringstream ss; - ss << i; - auto it_child = variant->map().find(ss.str()); - if (it_child != variant->map().end()) { - array_result.vector()[i] = it_child->second; - } - } - *variant = array_result; } + *variant = array_result; } } @@ -826,13 +835,55 @@ UtilLeafType GetLeafType(Variant::Type type) { // Store the pointers to the key and the value Variant in a map for sorting typedef std::pair NodeSortingData; +int ChildKeyCompareTo(const Variant& left, const Variant& right) { + const Variant kMinChildKey(QueryParamsComparator::kMinKey); + const Variant kMaxChildKey(QueryParamsComparator::kMaxKey); + + FIREBASE_DEV_ASSERT(left.is_string()); + FIREBASE_DEV_ASSERT(right.is_string()); + + if (left == right) { + return 0; + } else if (left == kMinChildKey || right == kMaxChildKey) { + return -1; + } else if (right == kMinChildKey || left == kMaxChildKey) { + return 1; + } else { + int64_t left_int_key = -1; + bool left_is_int = ParseInteger(left.string_value(), &left_int_key); + int64_t right_int_key = -1; + bool right_is_int = ParseInteger(right.string_value(), &right_int_key); + if (left_is_int) { + if (right_is_int) { + int cmp = left_int_key - right_int_key; + return cmp == 0 ? (strlen(left.string_value()) - + strlen(right.string_value())) + : cmp; + } else { + return -1; + } + } else if (right_is_int) { + return 1; + } else { + return left > right ? 1 : -1; + } + } +} + // Private function to serialize all child nodes void ProcessChildNodes(std::stringstream* ss, std::vector* nodes, bool saw_priority) { - // If any node has priority, sort the vector + // If any node has priority, sort using priority. if (saw_priority) { QueryParams params; + assert(params.order_by == QueryParams::kOrderByPriority); std::sort(nodes->begin(), nodes->end(), QueryParamsLesser(¶ms)); + } else { + // Otherwise, use default sorting function. + std::sort(nodes->begin(), nodes->end(), + [](const NodeSortingData& left, const NodeSortingData& right) { + return ChildKeyCompareTo(*left.first, *right.first) < 0; + }); } // Serialize each child with its key and its hashed value @@ -852,15 +903,21 @@ void AppendHashRepAsContainer(std::stringstream* ss, const Variant& data) { std::vector nodes; if (data.is_vector()) { - // Convert vector into map before process it. The map use index string as - // the key. - Variant map = Variant::EmptyMap(); + // Store index as string to be used for hash calculating, since + // NodeSortingData stores the pointer to the Variant. + // This is to avoid making copies of Variant from data. + std::vector index_variants; + index_variants.reserve(data.vector().size()); + bool saw_priority = false; for (int i = 0; i < data.vector().size(); ++i) { std::stringstream index_stream; index_stream << i; - map.map()[index_stream.str()] = data.vector()[i]; + index_variants.push_back(index_stream.str()); + nodes.push_back(NodeSortingData(&index_variants[i], &data.vector()[i])); + saw_priority = + saw_priority || !GetVariantPriority(data.vector()[i]).is_null(); } - AppendHashRepAsContainer(ss, map); + ProcessChildNodes(ss, &nodes, saw_priority); } else if (data.is_map()) { bool saw_priority = false; for (auto& it_child : data.map()) { diff --git a/database/src/desktop/util_desktop.h b/database/src/desktop/util_desktop.h index 4e6e0dc0f7..98c8cb13ae 100644 --- a/database/src/desktop/util_desktop.h +++ b/database/src/desktop/util_desktop.h @@ -188,11 +188,11 @@ size_t GetEffectiveChildren(const Variant& variant, // Check if the variant or any of its children is vector. bool HasVector(const Variant& variant); -// Parse a base-ten input string into 32-bit integer. Strings are parsed as +// Parse a base-ten input string into 64-bit integer. Strings are parsed as // integers if they do not have leading 0's - if they do they are simply treated // as strings. This is done to match the behavior of the other database // implementations. -bool ParseInteger(const char* str, int32_t* output); +bool ParseInteger(const char* str, int64_t* output); // Prune the priorities and convert map into vector if applicable, to the // variant and its children. This function assumes the variant or its children @@ -254,6 +254,20 @@ bool VariantsAreEquivalent(const Variant& a, const Variant& b); // using the input string. const std::string& GetBase64SHA1(const std::string& input, std::string* output); +// Compare two Variant as child keys for sorting. +// Expect both Variants are string. +// Returns 0 if both Variants are equal. +// Returns greater than 0 if left is greater than right. +// Returns less than 0 if left is less than right. +// Child key comparison is based on the following rules. +// 1. "[MAX_KEY]" is greater than everything +// 2. "[MIN_KEY]" is less than everything +// 3. Integer key is less than string key +// 4. If both keys are integer and are equal, ex. "1" and "001", one with the +// shorter length as a string is less than the other. +// 5. Otherwise, compare as a string. +int ChildKeyCompareTo(const Variant& left, const Variant& right); + // Returns serialized string of a Variant to be used for GetHash() function. // The implementation is based on Node.java and its derived classes from Android // SDK diff --git a/database/src/desktop/view/view_processor.cc b/database/src/desktop/view/view_processor.cc index 4767c717f0..0fea069d6c 100644 --- a/database/src/desktop/view/view_processor.cc +++ b/database/src/desktop/view/view_processor.cc @@ -415,12 +415,13 @@ ViewCache ViewProcessor::ApplyServerOverwrite( // Apply the server overwrite to the appropriate child. Path child_change_path = change_path.PopFrontDirectory(); // Get a copy of the child (if present) so that it can be mutated. - Variant new_child_node = old_server_snap.variant(); + Variant new_child_node = + VariantGetChild(&old_server_snap.variant(), child_key); VariantUpdateChild(&new_child_node, child_change_path, changed_snap); if (IsPriorityKey(child_key.str())) { // If this is a priority node, update the priority on the indexed node. - new_server_cache = server_filter->UpdatePriority( - old_server_snap.indexed_variant(), new_child_node); + new_server_cache = + server_filter->UpdatePriority(new_server_cache, new_child_node); } else { // If this is a regular update, the update through the filter to make sure // we get only the values that are not filtered by the query spec. diff --git a/database/src/include/firebase/database/mutable_data.h b/database/src/include/firebase/database/mutable_data.h index d02b44fe3d..5d49aaa788 100644 --- a/database/src/include/firebase/database/mutable_data.h +++ b/database/src/include/firebase/database/mutable_data.h @@ -126,7 +126,7 @@ class MutableData { /// /// @param[in] path Path relative to this data's location. /// @returns True if there is data at the specified location, false if not. - bool HasChild(const std::string& path); + bool HasChild(const std::string& path) const; /// @brief Sets the data at this location to the given value. /// @@ -164,7 +164,7 @@ class MutableData { friend MutableData GetInvalidMutableData(); /// @endcond - MutableData(internal::MutableDataInternal* internal); + explicit MutableData(internal::MutableDataInternal* internal); internal::MutableDataInternal* internal_; }; diff --git a/dynamic_links/CMakeLists.txt b/dynamic_links/CMakeLists.txt index 8b0e25d0d1..50d91d7664 100644 --- a/dynamic_links/CMakeLists.txt +++ b/dynamic_links/CMakeLists.txt @@ -91,7 +91,7 @@ if(IOS) set(pod_target_name "download_dynamic_links_pod_headers") set(pods_dir "${PROJECT_BINARY_DIR}/Pods") set(pod_list "") - list(APPEND pod_list "'Firebase/DynamicLinks', '6.9.0'") + list(APPEND pod_list "'Firebase/DynamicLinks', '6.10.0'") setup_pod_headers_target("${pod_target_name}" "${pods_dir}" "${pod_list}") diff --git a/dynamic_links/src/dynamic_links_android.cc b/dynamic_links/src/dynamic_links_android.cc index 24c6bd9cbd..370cc07262 100644 --- a/dynamic_links/src/dynamic_links_android.cc +++ b/dynamic_links/src/dynamic_links_android.cc @@ -871,7 +871,7 @@ GeneratedDynamicLink GetLongLink(const DynamicLinkComponents& components) { } static void FutureShortLinkCallback(JNIEnv* jni_env, jobject result, - util::FutureResult result_code, int status, + util::FutureResult result_code, const char* status_message, void* callback_data) { if (result_code == util::kFutureResultSuccess) { diff --git a/functions/CMakeLists.txt b/functions/CMakeLists.txt index 91f6f4436f..cf83a7a82d 100644 --- a/functions/CMakeLists.txt +++ b/functions/CMakeLists.txt @@ -19,6 +19,7 @@ set (CMAKE_CXX_STANDARD 11) include(binary_to_array) include(download_pod_headers) +include(firebase_cpp_gradle) project(firebase_functions NONE) enable_language(C) @@ -97,7 +98,9 @@ else() add_definitions(-include assert.h -include string.h) endif() -if(IOS) +if(ANDROID) + firebase_cpp_proguard_file(functions) +elseif(IOS) # Enable Automatic Reference Counting (ARC). set_property( TARGET firebase_functions @@ -108,8 +111,8 @@ if(IOS) set(pod_target_name "download_functions_pod_headers") set(pods_dir "${PROJECT_BINARY_DIR}/Pods") set(pod_list "") - list(APPEND pod_list "'Firebase/Core', '6.9.0'") - list(APPEND pod_list "'Firebase/Functions', '6.9.0'") + list(APPEND pod_list "'Firebase/Core', '6.10.0'") + list(APPEND pod_list "'Firebase/Functions', '6.10.0'") setup_pod_headers_target("${pod_target_name}" "${pods_dir}" "${pod_list}") diff --git a/functions/src/android/callable_reference_android.cc b/functions/src/android/callable_reference_android.cc index 8fa7627b67..75618f41ac 100644 --- a/functions/src/android/callable_reference_android.cc +++ b/functions/src/android/callable_reference_android.cc @@ -15,6 +15,7 @@ #include "functions/src/android/callable_reference_android.h" #include + #include "app/src/include/firebase/app.h" #include "app/src/include/firebase/variant.h" #include "app/src/util_android.h" @@ -142,7 +143,7 @@ struct FutureCallbackData { // Universal callback handler. void HttpsCallableReferenceInternal::FutureCallback( JNIEnv* env, jobject java_result, util::FutureResult result_code, - int status, const char* status_message, void* callback_data) { + const char* status_message, void* callback_data) { auto data = reinterpret_cast(callback_data); assert(data != nullptr); if (result_code != util::kFutureResultSuccess) { diff --git a/functions/src/android/callable_reference_android.h b/functions/src/android/callable_reference_android.h index c4a5d18562..4643b07126 100644 --- a/functions/src/android/callable_reference_android.h +++ b/functions/src/android/callable_reference_android.h @@ -16,6 +16,7 @@ #define FIREBASE_FUNCTIONS_CLIENT_CPP_SRC_ANDROID_CALLABLE_REFERENCE_ANDROID_H_ #include + #include "app/src/include/firebase/app.h" #include "app/src/include/firebase/future.h" #include "app/src/include/firebase/internal/common.h" @@ -68,7 +69,7 @@ class HttpsCallableReferenceInternal { private: static void FutureCallback(JNIEnv* env, jobject result, - util::FutureResult result_code, int status, + util::FutureResult result_code, const char* status_message, void* callback_data); ReferenceCountedFutureImpl* future(); diff --git a/instance_id/CMakeLists.txt b/instance_id/CMakeLists.txt index 3a90067558..a2e655b2ec 100644 --- a/instance_id/CMakeLists.txt +++ b/instance_id/CMakeLists.txt @@ -19,6 +19,7 @@ set (CMAKE_CXX_STANDARD 11) include(binary_to_array) include(download_pod_headers) +include(firebase_cpp_gradle) project(firebase_instance_id NONE) enable_language(C) @@ -94,7 +95,9 @@ else() add_definitions(-include assert.h -include string.h) endif() -if(IOS) +if(ANDROID) + firebase_cpp_proguard_file(instance_id) +elseif(IOS) # Enable Automatic Reference Counting (ARC). set_property( TARGET firebase_instance_id @@ -105,7 +108,7 @@ if(IOS) set(pod_target_name "download_instance_id_pod_headers") set(pods_dir "${PROJECT_BINARY_DIR}/Pods") set(pod_list "") - list(APPEND pod_list "'Firebase/Core', '6.9.0'") + list(APPEND pod_list "'Firebase/Core', '6.10.0'") list(APPEND pod_list "'FirebaseInstanceID', '4.2.5'") setup_pod_headers_target("${pod_target_name}" "${pods_dir}" "${pod_list}") diff --git a/messaging/CMakeLists.txt b/messaging/CMakeLists.txt index e58eed515b..42dce19165 100644 --- a/messaging/CMakeLists.txt +++ b/messaging/CMakeLists.txt @@ -19,6 +19,7 @@ set (CMAKE_CXX_STANDARD 11) include(binary_to_array) include(download_pod_headers) +include(firebase_cpp_gradle) project(firebase_messaging NONE) enable_language(C) @@ -113,7 +114,9 @@ else() add_definitions(-include assert.h -include string.h) endif() -if(IOS) +if(ANDROID) + firebase_cpp_proguard_file(messaging) +elseif(IOS) # Enable Automatic Reference Counting (ARC). set_property( TARGET firebase_messaging @@ -124,8 +127,8 @@ if(IOS) set(pod_target_name "download_messaging_pod_headers") set(pods_dir "${PROJECT_BINARY_DIR}/Pods") set(pod_list "") - list(APPEND pod_list "'Firebase/Core', '6.9.0'") - list(APPEND pod_list "'Firebase/Messaging', '6.9.0'") + list(APPEND pod_list "'Firebase/Core', '6.10.0'") + list(APPEND pod_list "'Firebase/Messaging', '6.10.0'") setup_pod_headers_target("${pod_target_name}" "${pods_dir}" "${pod_list}") diff --git a/messaging/src/android/cpp/messaging.cc b/messaging/src/android/cpp/messaging.cc index 64b358ebe3..82a45085fc 100644 --- a/messaging/src/android/cpp/messaging.cc +++ b/messaging/src/android/cpp/messaging.cc @@ -506,8 +506,7 @@ static void FireIntentMessage(JNIEnv* env) { } // Intent intent = app.getIntent(); jobject intent = env->CallObjectMethod( - activity, - util::activity::GetMethodId(util::activity::kGetIntent)); + activity, util::activity::GetMethodId(util::activity::kGetIntent)); assert(env->ExceptionCheck() == false); env->DeleteLocalRef(activity); @@ -793,7 +792,7 @@ void Send(const Message& message) { static void SubscriptionUpdateComplete(JNIEnv* env, jobject result, util::FutureResult result_code, - int status, const char* status_message, + const char* status_message, void* callback_data) { SafeFutureHandle* handle = reinterpret_cast*>(callback_data); diff --git a/release_build_files/Android/firebase_dependencies.gradle b/release_build_files/Android/firebase_dependencies.gradle index a65091c580..500bf67575 100644 --- a/release_build_files/Android/firebase_dependencies.gradle +++ b/release_build_files/Android/firebase_dependencies.gradle @@ -20,16 +20,16 @@ def firebaseDependenciesMap = [ 'admob' : ['com.google.firebase:firebase-ads:18.2.0', 'com.google.android.gms:play-services-measurement-sdk-api:17.2.0'], 'analytics' : ['com.google.firebase:firebase-analytics:17.2.0'], - 'auth' : ['com.google.firebase:firebase-auth:19.0.0'], - 'database' : ['com.google.firebase:firebase-database:19.1.0'], + 'auth' : ['com.google.firebase:firebase-auth:19.1.0'], + 'database' : ['com.google.firebase:firebase-database:19.2.0'], 'dynamic_links' : ['com.google.firebase:firebase-dynamic-links:19.0.0'], - 'firestore' : ['com.google.firebase:firebase-firestore:21.1.1'], + 'firestore' : ['com.google.firebase:firebase-firestore:21.2.0'], 'functions' : ['com.google.firebase:firebase-functions:19.0.1'], 'instance_id' : ['com.google.firebase:firebase-iid:20.0.0'], 'messaging' : ['com.google.firebase.messaging.cpp:firebase_messaging_cpp@aar', 'com.google.firebase:firebase-messaging:20.0.0'], - 'remote_config' : ['com.google.firebase:firebase-config:19.0.1'], - 'storage' : ['com.google.firebase:firebase-storage:19.0.1'] + 'remote_config' : ['com.google.firebase:firebase-config:19.0.3'], + 'storage' : ['com.google.firebase:firebase-storage:19.1.0'] ] // Handles adding the Firebase C++ dependencies as properties. diff --git a/remote_config/CMakeLists.txt b/remote_config/CMakeLists.txt index 5c28542376..90c2e6a6f4 100644 --- a/remote_config/CMakeLists.txt +++ b/remote_config/CMakeLists.txt @@ -18,6 +18,7 @@ cmake_minimum_required (VERSION 3.1) set (CMAKE_CXX_STANDARD 11) include(download_pod_headers) +include(firebase_cpp_gradle) project(firebase_remote_config NONE) enable_language(C) @@ -47,6 +48,8 @@ else() # Generates config.pb.c and config.pb.h NANOPB_GENERATE_CPP(PROTO_SRCS PROTO_HDRS src_protos/config.proto) set(desktop_SRCS + ${PROTO_SRCS} + ${PROTO_HDRS} src/desktop/remote_config.cc src/desktop/rest.cc src/desktop/config_data.cc @@ -82,8 +85,7 @@ else() set(additional_link_LIB firebase_rest_lib firebase_instance_id_desktop_impl - flatbuffers - protobuf-nanopb) + flatbuffers) endif() add_library(firebase_remote_config STATIC @@ -118,7 +120,9 @@ else() add_definitions(-include assert.h -include string.h) endif() -if(IOS) +if(ANDROID) + firebase_cpp_proguard_file(remote_config) +elseif(IOS) # Enable Automatic Reference Counting (ARC). set_property( TARGET firebase_remote_config @@ -129,8 +133,8 @@ if(IOS) set(pod_target_name "download_remote_config_pod_headers") set(pods_dir "${PROJECT_BINARY_DIR}/Pods") set(pod_list "") - list(APPEND pod_list "'Firebase/Core', '6.9.0'") - list(APPEND pod_list "'Firebase/RemoteConfig', '6.9.0'") + list(APPEND pod_list "'Firebase/Core', '6.10.0'") + list(APPEND pod_list "'Firebase/RemoteConfig', '6.10.0'") setup_pod_headers_target("${pod_target_name}" "${pods_dir}" "${pod_list}") @@ -139,7 +143,7 @@ if(IOS) target_include_directories(firebase_remote_config PRIVATE ${base_header_dir}/FirebaseCore - ${base_header_dir}/FirebaseRemoteConfig/FirebaseRemoteConfig + ${base_header_dir}/FirebaseRemoteConfig ) # Add a dependency to downloading the headers onto remote_config. diff --git a/remote_config/src/remote_config_android.cc b/remote_config/src/remote_config_android.cc index 54d1a4ea1d..c7fe3a85a0 100644 --- a/remote_config/src/remote_config_android.cc +++ b/remote_config/src/remote_config_android.cc @@ -663,9 +663,9 @@ Future Fetch() { return Fetch(kDefaultCacheExpiration); } // Complete a pending future. static void FutureCallback(JNIEnv* env, jobject result, - util::FutureResult result_code, int status, + util::FutureResult result_code, const char* status_message, void* callback_data) { - bool success = status == util::kFutureResultSuccess; + bool success = (result_code == util::kFutureResultSuccess); if (!success && result) { if (env->IsInstanceOf(result, throttled_exception::GetClass())) { g_throttled_end_time = static_cast(env->CallLongMethod( diff --git a/storage/CMakeLists.txt b/storage/CMakeLists.txt index d44dc37b9d..44c5aed3c9 100644 --- a/storage/CMakeLists.txt +++ b/storage/CMakeLists.txt @@ -17,7 +17,9 @@ cmake_minimum_required (VERSION 3.1) set (CMAKE_CXX_STANDARD 11) +include(binary_to_array) include(download_pod_headers) +include(firebase_cpp_gradle) project(firebase_storage NONE) enable_language(C) @@ -34,6 +36,8 @@ set(common_SRCS src/common/storage_uri_parser.cc) # Define the resource build needed for Android +firebase_cpp_gradle(":storage:storage_resources:generateDexJarRelease" + "${CMAKE_CURRENT_LIST_DIR}/storage_resources/build/dexed.jar") binary_to_array("storage_resources" "${CMAKE_CURRENT_LIST_DIR}/storage_resources/build/dexed.jar" "firebase_storage_resources" @@ -122,7 +126,9 @@ else() add_definitions(-include assert.h -include string.h) endif() -if(IOS) +if(ANDROID) + firebase_cpp_proguard_file(storage) +elseif(IOS) # Enable Automatic Reference Counting (ARC) set_property( TARGET firebase_storage @@ -133,8 +139,8 @@ if(IOS) set(pod_target_name "download_storage_pod_headers") set(pods_dir "${PROJECT_BINARY_DIR}/Pods") set(pod_list "") - list(APPEND pod_list "'Firebase/Core', '6.9.0'") - list(APPEND pod_list "'Firebase/Storage', '6.9.0'") + list(APPEND pod_list "'Firebase/Core', '6.10.0'") + list(APPEND pod_list "'Firebase/Storage', '6.10.0'") setup_pod_headers_target("${pod_target_name}" "${pods_dir}" "${pod_list}") diff --git a/storage/src/android/storage_reference_android.cc b/storage/src/android/storage_reference_android.cc index bf44adef06..d19c68303c 100644 --- a/storage/src/android/storage_reference_android.cc +++ b/storage/src/android/storage_reference_android.cc @@ -224,7 +224,6 @@ struct FutureCallbackData { // and completes a different typed Future depending on that type. void StorageReferenceInternal::FutureCallback(JNIEnv* env, jobject result, util::FutureResult result_code, - int status, const char* status_message, void* callback_data) { FutureCallbackData* data = @@ -241,8 +240,7 @@ void StorageReferenceInternal::FutureCallback(JNIEnv* env, jobject result, (result_code == util::kFutureResultCancelled) ? kErrorCancelled : data->storage->ErrorFromJavaStorageException(result, &message); - LogDebug("FutureCallback: Completing a Future with an error (%d, %d).", - status, code); + LogDebug("FutureCallback: Completing a Future with an error (%d).", code); if (data->func == kStorageReferenceFnPutFile || data->func == kStorageReferenceFnPutBytes || data->func == kStorageReferenceFnGetMetadata || diff --git a/storage/src/android/storage_reference_android.h b/storage/src/android/storage_reference_android.h index 79f6e7606d..062164e11f 100644 --- a/storage/src/android/storage_reference_android.h +++ b/storage/src/android/storage_reference_android.h @@ -16,6 +16,7 @@ #define FIREBASE_STORAGE_CLIENT_CPP_SRC_ANDROID_STORAGE_REFERENCE_ANDROID_H_ #include + #include "app/src/include/firebase/app.h" #include "app/src/include/firebase/future.h" #include "app/src/include/firebase/internal/common.h" @@ -154,7 +155,7 @@ class StorageReferenceInternal { private: static void FutureCallback(JNIEnv* env, jobject result, - util::FutureResult result_code, int status, + util::FutureResult result_code, const char* status_message, void* callback_data); // If `listener` is not nullptr, create a Java listener class for it and diff --git a/storage/src/common/storage.cc b/storage/src/common/storage.cc index 5799eab1dd..afe4564e77 100644 --- a/storage/src/common/storage.cc +++ b/storage/src/common/storage.cc @@ -184,15 +184,13 @@ StorageReference Storage::GetReferenceFromUrl(const char* url) const { static const char kObjectName[] = "StorageReference"; // Extract components from this storage object's URL. - std::string this_bucket; - internal::UriToComponents(const_cast(this)->url(), kObjectName, - &this_bucket, nullptr); + std::string this_bucket = const_cast(this)->GetReference().bucket(); // Make sure the specified URL is valid. std::string bucket; bool valid = internal::UriToComponents(std::string(url), kObjectName, &bucket, nullptr); if (valid) { - if (bucket != this_bucket) { + if (!this_bucket.empty() && bucket != this_bucket) { LogError( "Unable to create %s from URL %s. " "URL specifies a different bucket (%s) than this instance (%s)", diff --git a/storage/src/desktop/storage_desktop.cc b/storage/src/desktop/storage_desktop.cc index 1e67d4d44e..10ed0896b5 100644 --- a/storage/src/desktop/storage_desktop.cc +++ b/storage/src/desktop/storage_desktop.cc @@ -32,19 +32,20 @@ namespace internal { StorageInternal::StorageInternal(App* app, const char* url) { app_ = app; + if (url) { url_ = url; + root_ = StoragePath(url_); } else { const char* bucket = app->options().storage_bucket(); - if (bucket) url_ = std::string(kGsScheme) + bucket; + root_ = StoragePath(bucket ? std::string(kGsScheme) + bucket : ""); } - root_ = StoragePath(url_); // LINT.IfChange max_download_retry_time_ = 600.0; max_operation_retry_time_ = 120.0; max_upload_retry_time_ = 600.0; - // LINT.ThenChange(//depot_android_gmscore_dev/\ + // LINT.ThenChange(//depot/google3/java/com/google/android/gmscore/integ/\ // client/firebase-storage-api/src/com/google/firebase/\ // storage/FirebaseStorage.java, // //depot_firebase_ios_Releases/FirebaseStorage/\ @@ -126,8 +127,8 @@ void StorageInternal::CleanupCompletedOperations() { for (auto it = operations_.begin(); it != operations_.end(); ++it) { if ((*it)->is_complete()) operations_to_delete.push_back(*it); } - for (auto it = operations_to_delete.begin(); - it != operations_to_delete.end(); ++it) { + for (auto it = operations_to_delete.begin(); it != operations_to_delete.end(); + ++it) { RemoveOperation(*it); delete *it; } diff --git a/storage/src/desktop/storage_desktop.h b/storage/src/desktop/storage_desktop.h index 76410363c0..1f2811a3bf 100644 --- a/storage/src/desktop/storage_desktop.h +++ b/storage/src/desktop/storage_desktop.h @@ -39,7 +39,7 @@ class StorageInternal { // Get the firease::App that this Storage was created with. ::firebase::App* app() { return app_; } - // Return the URL we were created with. + // Return the URL we were created with, if we were created with it explicitly. std::string url() { return url_; } // Get a StorageReference to the root of the database. diff --git a/storage/storage_resources/build.gradle b/storage/storage_resources/build.gradle index eb3c0dcfb4..1a339541a1 100644 --- a/storage/storage_resources/build.gradle +++ b/storage/storage_resources/build.gradle @@ -46,7 +46,7 @@ android { dependencies { implementation 'com.google.firebase:firebase-analytics:17.2.0' - implementation 'com.google.firebase:firebase-storage:19.0.1' + implementation 'com.google.firebase:firebase-storage:19.1.0' } afterEvaluate { diff --git a/test_android.sh b/test_android.sh new file mode 100755 index 0000000000..e33dc1a5ef --- /dev/null +++ b/test_android.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Builds and runs the tests, meant to be used on a Linux environment. + +# Fail on any error. +set -e +# Display commands being run. +set -x + +./gradlew :app:assembleRelease + + diff --git a/test_mac_ios.sh b/test_mac_ios.sh new file mode 100755 index 0000000000..b215b75a99 --- /dev/null +++ b/test_mac_ios.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# +# Copyright 2019 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Builds and runs the tests, meant to be used on a Linux environment. + +# Fail on any error. +set -e +# Display commands being run. +set -x + +# Make a directory to work in +mkdir -p mac_ios_build +cd mac_ios_build + +# Configure cmake with tests enabled +# and disable use of libsecret due to not working on kokoro builders +cmake -DCMAKE_TOOLCHAIN_FILE=../cmake/ios.cmake .. -DFIREBASE_CPP_BUILD_TESTS=ON -DFIREBASE_FORCE_FAKE_SECURE_STORAGE=ON + +# Build the SDK and the tests +cmake --build . + +# Run the tests +ctest --verbose diff --git a/test_macos.sh b/test_mac_x64.sh similarity index 96% rename from test_macos.sh rename to test_mac_x64.sh index 708b1ae170..3ddbc7ad2f 100755 --- a/test_macos.sh +++ b/test_mac_x64.sh @@ -22,8 +22,8 @@ set -e set -x # Make a directory to work in -mkdir -p macos_build -cd macos_build +mkdir -p mac_x64_build +cd mac_x64_build # Configure cmake with tests enabled # and disable use of libsecret due to not working on kokoro builders diff --git a/test_windows.bat b/test_windows.bat deleted file mode 100644 index 2866cdbba2..0000000000 --- a/test_windows.bat +++ /dev/null @@ -1,22 +0,0 @@ -:: Make a directory to work in -mkdir windows_build -cd windows_build - -:: Configure cmake with tests enabled -:: TODO: Configure the dependencies for Database and Remote Config tests. -cmake .. -DFIREBASE_CPP_BUILD_TESTS=ON^ - -DFIREBASE_INCLUDE_REMOTE_CONFIG=OFF -DFIREBASE_INCLUDE_DATABASE=OFF - -:: Check for errors, and return if there were any -if %errorlevel% neq 0 exit /b %errorlevel% - -:: Build the SDK and the tests -cmake --build . - -:: Again, check for errors, and return if there were any -if %errorlevel% neq 0 exit /b %errorlevel% - -:: Run the tests -ctest --verbose - -exit /b %ERRORLEVEL% \ No newline at end of file diff --git a/test_windows_x32.bat b/test_windows_x32.bat new file mode 100644 index 0000000000..5984e04eb4 --- /dev/null +++ b/test_windows_x32.bat @@ -0,0 +1,73 @@ +@echo off + +REM !/bin/bash +REM +REM Copyright 2019 Google LLC +REM +REM Licensed under the Apache License, Version 2.0 (the "License"); +REM you may not use this file except in compliance with the License. +REM You may obtain a copy of the License at +REM +REM http://www.apache.org/licenses/LICENSE-2.0 +REM +REM Unless required by applicable law or agreed to in writing, software +REM distributed under the License is distributed on an "AS IS" BASIS, +REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +REM See the License for the specific language governing permissions and +REM limitations under the License. +REM +REM Builds and packs firebase unity meant to be used on a Windows environment. + +SETLOCAL + +SET status=0 +set PROTOBUF_SRC_ROOT_FOLDER=%PROTOBUF_SRC_ROOT_FOLDER:\=/% + +IF EXIST "C:\Program Files (x86)\OpenSSL-Win32" ( + SET OPENSSL_x32=C:/Program Files ^(x86^)/OpenSSL-Win32 +) ELSE ( + ECHO ERROR: Cant find open ssl x32 + EXIT /B -1 +) + +CALL :BUILD x32, "%OPENSSL_x32%", "" +if %errorlevel% neq 0 (SET status=%errorlevel%) + +EXIT /B %status% + +:BUILD + ECHO ################################################################# + DATE /T + TIME /T + ECHO Building config '%~1' with option '%~3'. + ECHO ################################################################# + + @echo on + + mkdir windows_%~1 + pushd windows_%~1 + + cmake .. -DFIREBASE_CPP_BUILD_TESTS=ON -DPROTOBUF_SRC_ROOT_FOLDER=%PROTOBUF_SRC_ROOT_FOLDER% -DOPENSSL_ROOT_DIR="%~2" %~3 + + :: Check for errors, and return if there were any + if %errorlevel% neq 0 ( + popd + @echo off + exit /b %errorlevel% + ) + + cmake --build . --config Release + + :: Again, check for errors, and return if there were any + if %errorlevel% neq 0 ( + popd + @echo off + exit /b %errorlevel% + ) + + :: Run the tests + ctest --verbose %CTEST_SKIP_FILTER% + + popd + @echo off +EXIT /B %errorlevel% \ No newline at end of file diff --git a/test_windows_x64.bat b/test_windows_x64.bat new file mode 100644 index 0000000000..cd12a32a9b --- /dev/null +++ b/test_windows_x64.bat @@ -0,0 +1,73 @@ +@echo off + +REM !/bin/bash +REM +REM Copyright 2019 Google LLC +REM +REM Licensed under the Apache License, Version 2.0 (the "License"); +REM you may not use this file except in compliance with the License. +REM You may obtain a copy of the License at +REM +REM http://www.apache.org/licenses/LICENSE-2.0 +REM +REM Unless required by applicable law or agreed to in writing, software +REM distributed under the License is distributed on an "AS IS" BASIS, +REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +REM See the License for the specific language governing permissions and +REM limitations under the License. +REM +REM Builds and packs firebase unity meant to be used on a Windows environment. + +SETLOCAL + +SET status=0 +set PROTOBUF_SRC_ROOT_FOLDER=%PROTOBUF_SRC_ROOT_FOLDER:\=/% + +IF EXIST "C:\Program Files\OpenSSL-Win64" ( + SET OPENSSL_x64=C:/Program Files/OpenSSL-Win64 +) ELSE ( + ECHO ERROR: Cant find open ssl x64 + EXIT /B -1 +) + +CALL :BUILD x64, "%OPENSSL_x64%", "-A x64" +if %errorlevel% neq 0 (SET status=%errorlevel%) + +EXIT /B %status% + +:BUILD + ECHO ################################################################# + DATE /T + TIME /T + ECHO Building config '%~1' with option '%~3'. + ECHO ################################################################# + + @echo on + + mkdir windows_%~1 + pushd windows_%~1 + + cmake .. -DFIREBASE_CPP_BUILD_TESTS=ON -DPROTOBUF_SRC_ROOT_FOLDER=%PROTOBUF_SRC_ROOT_FOLDER% -DOPENSSL_ROOT_DIR="%~2" %~3 + + :: Check for errors, and return if there were any + if %errorlevel% neq 0 ( + popd + @echo off + exit /b %errorlevel% + ) + + cmake --build . --config Release + + :: Again, check for errors, and return if there were any + if %errorlevel% neq 0 ( + popd + @echo off + exit /b %errorlevel% + ) + + :: Run the tests + ctest --verbose %CTEST_SKIP_FILTER% + + popd + @echo off +EXIT /B %errorlevel% \ No newline at end of file diff --git a/testing/reporter.cc b/testing/reporter.cc index c45b3ca07e..fc0494ed2c 100644 --- a/testing/reporter.cc +++ b/testing/reporter.cc @@ -5,6 +5,10 @@ #include #include +#ifdef __APPLE__ +#include "TargetConditionals.h" +#endif + #include "testing/reporter.h" namespace firebase { @@ -96,7 +100,7 @@ void Reporter::addExpectation(const ReportRow& expectation) { #if defined(FIREBASE_ANDROID_FOR_DESKTOP) || defined(__ANDROID__) if (expectation.getPlatform() == kAndroid) expectations_.push_back(expectation); -#elif defined(__APPLE__) +#elif defined(__APPLE__) && TARGET_OS_IPHONE if (expectation.getPlatform() == kIos) expectations_.push_back(expectation); #endif // defined(FIREBASE_ANDROID_FOR_DESKTOP) || defined(__ANDROID__) } diff --git a/testing/util_ios.mm b/testing/util_ios.mm index 2cffaca1e6..48ac3557f3 100644 --- a/testing/util_ios.mm +++ b/testing/util_ios.mm @@ -10,8 +10,14 @@ CallbackTicker::CallbackTicker(NSString* config_key, ParamCallback completion, id param, int error_code) - : eta_(0), error_(nil), has_param_(true), param_(nil), completion_(nil), - param_completion_(completion), key_(config_key), error_code_(error_code) { + : key_(config_key), + eta_(0), + error_(nil), + has_param_(true), + param_(nil), + error_code_(error_code), + completion_(nil), + param_completion_(completion) { const ConfigRow* row = ConfigGet([config_key UTF8String]); if (row == nullptr) { @@ -27,8 +33,13 @@ } CallbackTicker::CallbackTicker(NSString* config_key, Callback completion, int error_code) - : eta_(0), error_(nil), has_param_(false), param_(nil), completion_(completion), - param_completion_(nil), error_code_(error_code) { + : eta_(0), + error_(nil), + has_param_(false), + param_(nil), + error_code_(error_code), + completion_(completion), + param_completion_(nil) { const ConfigRow* row = ConfigGet([config_key UTF8String]); if (row == nullptr) { diff --git a/version.sh b/version.sh index 31a24ea447..8a745895e4 100755 --- a/version.sh +++ b/version.sh @@ -18,9 +18,9 @@ # Used to generate Pod dependencies in the Firebase Unity plugin. declare -A POD_SDK_VERSIONS=( - ["released"]="6.9.0" # gen_build.sh: [FIR] Managed by a script. DO NOT EDIT. - ["stable"]="6.9.0" # gen_build.sh: [FIR] Managed by a script. DO NOT EDIT. - ["head"]="6.9.0" # gen_build.sh: [FIR] Managed by a script. DO NOT EDIT. + ["released"]="6.10.0" # gen_build.sh: [FIR] Managed by a script. DO NOT EDIT. + ["stable"]="6.10.0" # gen_build.sh: [FIR] Managed by a script. DO NOT EDIT. + ["head"]="6.10.0" # gen_build.sh: [FIR] Managed by a script. DO NOT EDIT. ) help() {