[CHORE] Update react-native-firebase (#2336)

* Remove firebase

* Install firebase/app

* Install analytics

* Crashlytics

* Android

* Fix mocks

* Edit scheme to Debug build configuration

Co-authored-by: Djorkaeff Alexandre <djorkaeff.unb@gmail.com>
This commit is contained in:
Diego Mello 2020-07-24 10:19:17 -03:00 committed by GitHub
parent a00e3c7769
commit c91cd0b963
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
824 changed files with 56159 additions and 24958 deletions

View File

@ -0,0 +1,3 @@
export default {
crashlytics: null
};

View File

@ -1,7 +1,7 @@
apply plugin: "com.android.application"
apply plugin: 'com.google.gms.google-services'
apply plugin: 'com.google.firebase.crashlytics'
apply plugin: 'kotlin-android'
apply plugin: "io.fabric"
apply plugin: "com.google.firebase.firebase-perf"
apply plugin: 'com.bugsnag.android.gradle'
import com.android.build.OutputFile
@ -168,6 +168,9 @@ android {
minifyEnabled enableProguardInReleaseBuilds
setProguardFiles([getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'])
signingConfig signingConfigs.release
firebaseCrashlytics {
nativeSymbolUploadEnabled true
}
}
}
@ -215,11 +218,6 @@ dependencies {
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+" // From node_modules
implementation "com.google.firebase:firebase-messaging:18.0.0"
implementation "com.google.firebase:firebase-core:16.0.9"
implementation "com.google.firebase:firebase-perf:17.0.2"
implementation('com.crashlytics.sdk.android:crashlytics:2.9.9@aar') {
transitive = true
}
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
exclude group:'com.facebook.fbjni'
@ -251,5 +249,4 @@ task copyDownloadableDepsToLibs(type: Copy) {
into 'libs'
}
apply plugin: 'com.google.gms.google-services'
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)

View File

@ -29,10 +29,6 @@ import com.wix.reactnativenotifications.core.notification.INotificationsApplicat
import com.wix.reactnativenotifications.core.notification.IPushNotification;
import com.wix.reactnativekeyboardinput.KeyboardInputPackage;
import io.invertase.firebase.fabric.crashlytics.RNFirebaseCrashlyticsPackage;
import io.invertase.firebase.analytics.RNFirebaseAnalyticsPackage;
import io.invertase.firebase.perf.RNFirebasePerformancePackage;
import com.nozbe.watermelondb.WatermelonDBPackage;
import com.reactnativecommunity.viewpager.RNCViewPagerPackage;
@ -53,9 +49,6 @@ public class MainApplication extends Application implements ReactApplication, IN
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new RNFirebaseCrashlyticsPackage());
packages.add(new RNFirebaseAnalyticsPackage());
packages.add(new RNFirebasePerformancePackage());
packages.add(new KeyboardInputPackage(MainApplication.this));
packages.add(new RNNotificationsPackage(MainApplication.this));
packages.add(new WatermelonDBPackage());

View File

@ -20,8 +20,7 @@ buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:3.5.3'
classpath 'com.google.gms:google-services:4.2.0'
classpath 'io.fabric.tools:gradle:1.28.1'
classpath 'com.google.firebase:perf-plugin:1.2.1'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.0.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.bugsnag:bugsnag-android-gradle-plugin:4.+'

View File

@ -1,11 +1,12 @@
import { Client } from 'bugsnag-react-native';
import firebase from 'react-native-firebase';
import analytics from '@react-native-firebase/analytics';
import crashlytics from '@react-native-firebase/crashlytics';
import config from '../../../config';
import events from './events';
const bugsnag = new Client(config.BUGSNAG_API_KEY);
export const { analytics } = firebase;
export { analytics };
export const loggerConfig = bugsnag.config;
export const { leaveBreadcrumb } = bugsnag;
export { events };
@ -37,6 +38,7 @@ export default (e) => {
}
};
});
crashlytics().recordError(e);
} else {
console.log(e);
}

View File

@ -7,8 +7,6 @@ PODS:
- React
- CocoaAsyncSocket (7.6.4)
- CocoaLibEvent (1.0.0)
- Crashlytics (3.14.0):
- Fabric (~> 1.10.2)
- DoubleConversion (1.1.6)
- EXAppleAuthentication (2.2.1):
- UMCore
@ -41,7 +39,6 @@ PODS:
- UMFileSystemInterface
- EXWebBrowser (8.3.1):
- UMCore
- Fabric (1.10.2)
- FBLazyVector (0.63.1)
- FBReactNativeSpec (0.63.1):
- Folly (= 2020.01.13.00)
@ -50,11 +47,16 @@ PODS:
- React-Core (= 0.63.1)
- React-jsi (= 0.63.1)
- ReactCommon/turbomodule/core (= 0.63.1)
- Firebase/Core (6.28.1):
- Firebase/Analytics (6.27.1):
- Firebase/Core
- Firebase/Core (6.27.1):
- Firebase/CoreOnly
- FirebaseAnalytics (= 6.6.2)
- Firebase/CoreOnly (6.28.1):
- FirebaseCore (= 6.9.1)
- Firebase/CoreOnly (6.27.1):
- FirebaseCore (= 6.8.1)
- Firebase/Crashlytics (6.27.1):
- Firebase/CoreOnly
- FirebaseCrashlytics (~> 4.2.0)
- FirebaseAnalytics (6.6.2):
- FirebaseCore (~> 6.8)
- FirebaseInstallations (~> 1.4)
@ -64,15 +66,22 @@ PODS:
- GoogleUtilities/Network (~> 6.0)
- "GoogleUtilities/NSData+zlib (~> 6.0)"
- nanopb (~> 1.30905.0)
- FirebaseCore (6.9.1):
- FirebaseCore (6.8.1):
- FirebaseCoreDiagnostics (~> 1.3)
- GoogleUtilities/Environment (~> 6.7)
- GoogleUtilities/Logger (~> 6.7)
- FirebaseCoreDiagnostics (1.5.0):
- GoogleDataTransport (~> 7.0)
- GoogleUtilities/Environment (~> 6.7)
- GoogleUtilities/Logger (~> 6.7)
- GoogleUtilities/Environment (~> 6.5)
- GoogleUtilities/Logger (~> 6.5)
- FirebaseCoreDiagnostics (1.4.0):
- GoogleDataTransportCCTSupport (~> 3.1)
- GoogleUtilities/Environment (~> 6.5)
- GoogleUtilities/Logger (~> 6.5)
- nanopb (~> 1.30905.0)
- FirebaseCrashlytics (4.2.0):
- FirebaseCore (~> 6.8)
- FirebaseInstallations (~> 1.1)
- GoogleDataTransport (~> 6.1)
- GoogleDataTransportCCTSupport (~> 3.1)
- nanopb (~> 1.30905.0)
- PromisesObjC (~> 1.2)
- FirebaseInstallations (1.5.0):
- FirebaseCore (~> 6.8)
- GoogleUtilities/Environment (~> 6.7)
@ -140,7 +149,9 @@ PODS:
- GoogleUtilities/Network (~> 6.0)
- "GoogleUtilities/NSData+zlib (~> 6.0)"
- nanopb (~> 1.30905.0)
- GoogleDataTransport (7.0.0):
- GoogleDataTransport (6.2.1)
- GoogleDataTransportCCTSupport (3.2.0):
- GoogleDataTransport (~> 6.1)
- nanopb (~> 1.30905.0)
- GoogleUtilities/AppDelegateSwizzler (6.7.1):
- GoogleUtilities/Environment
@ -453,15 +464,18 @@ PODS:
- React
- SDWebImage (~> 5.0)
- SDWebImageWebPCoder (~> 0.4.1)
- RNFirebase (5.6.0):
- Firebase/Core
- RNFBAnalytics (7.3.1):
- Firebase/Analytics (~> 6.27.0)
- React
- RNFirebase/Crashlytics (= 5.6.0)
- RNFirebase/Crashlytics (5.6.0):
- Crashlytics
- Fabric
- Firebase/Core
- RNFBApp
- RNFBApp (8.2.0):
- Firebase/CoreOnly (~> 6.27.0)
- React
- RNFBCrashlytics (8.1.2):
- Firebase/Core (~> 6.27.0)
- Firebase/Crashlytics (~> 6.27.0)
- React
- RNFBApp
- RNGestureHandler (1.6.1):
- React
- RNImageCropPicker (0.31.1):
@ -594,7 +608,9 @@ DEPENDENCIES:
- "RNDateTimePicker (from `../node_modules/@react-native-community/datetimepicker`)"
- RNDeviceInfo (from `../node_modules/react-native-device-info`)
- "RNFastImage (from `../node_modules/@rocket.chat/react-native-fast-image`)"
- RNFirebase (from `../node_modules/react-native-firebase/ios`)
- "RNFBAnalytics (from `../node_modules/@react-native-firebase/analytics`)"
- "RNFBApp (from `../node_modules/@react-native-firebase/app`)"
- "RNFBCrashlytics (from `../node_modules/@react-native-firebase/crashlytics`)"
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
- RNImageCropPicker (from `../node_modules/react-native-image-crop-picker`)
- RNLocalize (from `../node_modules/react-native-localize`)
@ -623,12 +639,11 @@ SPEC REPOS:
- boost-for-react-native
- CocoaAsyncSocket
- CocoaLibEvent
- Crashlytics
- Fabric
- Firebase
- FirebaseAnalytics
- FirebaseCore
- FirebaseCoreDiagnostics
- FirebaseCrashlytics
- FirebaseInstallations
- Flipper
- Flipper-DoubleConversion
@ -639,6 +654,7 @@ SPEC REPOS:
- FlipperKit
- GoogleAppMeasurement
- GoogleDataTransport
- GoogleDataTransportCCTSupport
- GoogleUtilities
- JitsiMeetSDK
- libwebp
@ -769,8 +785,12 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-device-info"
RNFastImage:
:path: "../node_modules/@rocket.chat/react-native-fast-image"
RNFirebase:
:path: "../node_modules/react-native-firebase/ios"
RNFBAnalytics:
:path: "../node_modules/@react-native-firebase/analytics"
RNFBApp:
:path: "../node_modules/@react-native-firebase/app"
RNFBCrashlytics:
:path: "../node_modules/@react-native-firebase/crashlytics"
RNGestureHandler:
:path: "../node_modules/react-native-gesture-handler"
RNImageCropPicker:
@ -821,7 +841,6 @@ SPEC CHECKSUMS:
BugsnagReactNative: 98fb350df4bb0c94cce903023531a1a5cc11fa51
CocoaAsyncSocket: 694058e7c0ed05a9e217d1b3c7ded962f4180845
CocoaLibEvent: 2fab71b8bd46dd33ddb959f7928ec5909f838e3f
Crashlytics: 540b7e5f5da5a042647227a5e3ac51d85eed06df
DoubleConversion: cde416483dac037923206447da6e1454df403714
EXAppleAuthentication: 5b3da71bada29e2423d8ea27e5538ef0d75aba62
EXAV: 86344030966e0da7e00556fbb97269d9ad16071d
@ -834,13 +853,13 @@ SPEC CHECKSUMS:
EXPermissions: 80ac3acbdb145930079810fe5b08c022b3428aa8
EXVideoThumbnails: f70bdc5511749f3181028f5000bcb7be203c631d
EXWebBrowser: d37a5ffdea1b65947352bc001dd9f732463725d4
Fabric: 706c8b8098fff96c33c0db69cbf81f9c551d0d74
FBLazyVector: a50434c875bd42f2b1c99c712bda892a1dc659c7
FBReactNativeSpec: 393853a536428e05a9da00b6290042f09809b15b
Firebase: ed042590caa0029392257529a8003c25ee82bc18
Firebase: 919186c8e119dd9372a45fd1dd17a8a942bc1892
FirebaseAnalytics: 5fa308e1b13f838d0f6dc74719ac2a72e8c5afc4
FirebaseCore: 687b8e6a0a4337b898a6326d68254c2f80c143af
FirebaseCoreDiagnostics: 7535fe695737f8c5b350584292a70b7f8ff0357b
FirebaseCore: 8cd4f8ea22075e0ee582849b1cf79d8816506085
FirebaseCoreDiagnostics: 4505e4d4009b1d93f605088ee7d7764d5f0d1c84
FirebaseCrashlytics: 7d0fa02ea8842cc4b2ab07b0735edafde1ad77d6
FirebaseInstallations: 3c520c951305cbf9ca54eb891ff9e6d1fd384881
Flipper: 33585e2d9810fe5528346be33bcf71b37bb7ae13
Flipper-DoubleConversion: 38631e41ef4f9b12861c67d17cb5518d06badc41
@ -852,7 +871,8 @@ SPEC CHECKSUMS:
Folly: b73c3869541e86821df3c387eb0af5f65addfab4
glog: 40a13f7840415b9a77023fbcae0f1e6f43192af3
GoogleAppMeasurement: 8cd1f289d60e629cf16ab03363b9e89c776b9651
GoogleDataTransport: 8a40cb194ad242b6f6dfe72c14fe40fc67c4dcd7
GoogleDataTransport: 9a8a16f79feffc7f42096743de2a7c4815e84020
GoogleDataTransportCCTSupport: 489c1265d2c85b68187a83a911913d190012158d
GoogleUtilities: e121a3867449ce16b0e35ddf1797ea7a389ffdf2
JitsiMeetSDK: 2984eac1343690bf1c0c72bde75b48b0148d0f79
KeyCommands: f66c535f698ed14b3d3a4e58859d79a827ea907e
@ -901,7 +921,9 @@ SPEC CHECKSUMS:
RNDateTimePicker: e386ff4ef3300964ed0cad97ce6f206e0effbfdb
RNDeviceInfo: ed8557a8bd6443cbc0ab5d375e6808a38a279744
RNFastImage: 35ae972d6727c84ee3f5c6897e07f84d0a3445e9
RNFirebase: 37daa9a346d070f9f6ee1f3b4aaf4c8e3b1d5d1c
RNFBAnalytics: dae6d7b280ba61c96e1bbdd34aca3154388f025e
RNFBApp: 6fd8a7e757135d4168bf033a8812c241af7363a0
RNFBCrashlytics: 88de72c2476b5868a892d9523b89b86c527c540e
RNGestureHandler: 8f09cd560f8d533eb36da5a6c5a843af9f056b38
RNImageCropPicker: 38865ab4af1b0b2146ad66061196bc0184946855
RNLocalize: b6df30cc25ae736d37874f9bce13351db2f56796
@ -931,4 +953,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 55c04243097892160d63f79f3a23157165b7ac68
COCOAPODS: 1.8.4
COCOAPODS: 1.9.3

View File

@ -1 +0,0 @@
We've now combined all our supported platforms into a single podspec. As a result, we moved our submit script to a new location for Cocoapods projects: ${PODS_ROOT}/Crashlytics/submit. To avoid breaking functionality that references the old location of the submit, we've placed this dummy script that calls to the correct location, while providing a helpful warning if it is invoked. This bridge for backwards compatibility will be removed in a future release, so please heed the warning!

View File

@ -1,6 +0,0 @@
if [[ -z $PODS_ROOT ]]; then
echo "error: The submit binary delivered by cocoapods is in a new location, under '$"{"PODS_ROOT"}"/Crashlytics/submit'. This script was put in place for backwards compatibility, but it relies on PODS_ROOT, which does not have a value in your current setup. Please update the path to the submit binary to fix this issue."
else
echo "warning: The submit script is now located at '$"{"PODS_ROOT"}"/Crashlytics/submit'. To remove this warning, update your path to point to this new location."
sh "${PODS_ROOT}/Crashlytics/submit" "$@"
fi

View File

@ -1,23 +0,0 @@
# Crashlytics
## Overview
[Crashlytics](https://firebase.google.com/docs/crashlytics/get-started?platform=ios) offers the most powerful, yet lightest weight crash reporting solution for iOS.
## Setup
To start using Crashlytics, there are two options:
1) The recommended way is to go to the [Firebase Crashlytics Docs](https://firebase.google.com/docs/crashlytics/get-started?platform=ios) and follow the directions there.
2) If you aren't using Firebase yet, go to [Fabric Kits](https://fabric.io/kits), and follow the directions for Crashlytics.
## Resources
* [API Reference](https://firebase.google.com/docs/reference/ios/crashlytics/api/reference/Classes)
* [Forums](https://stackoverflow.com/questions/tagged/google-fabric)
* [Website](https://firebase.google.com/docs/crashlytics)
* Follow us on Twitter: [@crashlytics](https://twitter.com/crashlytics)

Binary file not shown.

View File

@ -1,31 +0,0 @@
//
// ANSCompatibility.h
// AnswersKit
//
// Copyright (c) 2015 Crashlytics, Inc. All rights reserved.
//
#pragma once
#if !__has_feature(nullability)
#define nonnull
#define nullable
#define _Nullable
#define _Nonnull
#endif
#ifndef NS_ASSUME_NONNULL_BEGIN
#define NS_ASSUME_NONNULL_BEGIN
#endif
#ifndef NS_ASSUME_NONNULL_END
#define NS_ASSUME_NONNULL_END
#endif
#if __has_feature(objc_generics)
#define ANS_GENERIC_NSARRAY(type) NSArray<type>
#define ANS_GENERIC_NSDICTIONARY(key_type,object_key) NSDictionary<key_type, object_key>
#else
#define ANS_GENERIC_NSARRAY(type) NSArray
#define ANS_GENERIC_NSDICTIONARY(key_type,object_key) NSDictionary
#endif

View File

@ -1,210 +0,0 @@
//
// Answers.h
// Crashlytics
//
// Copyright (c) 2015 Crashlytics, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "ANSCompatibility.h"
NS_ASSUME_NONNULL_BEGIN
/**
* This class exposes the Answers Events API, allowing you to track key
* user user actions and metrics in your app.
*/
@interface Answers : NSObject
/**
* Log a Sign Up event to see users signing up for your app in real-time, understand how
* many users are signing up with different methods and their success rate signing up.
*
* @param signUpMethodOrNil The method by which a user logged in, e.g. Twitter or Digits.
* @param signUpSucceededOrNil The ultimate success or failure of the login
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logSignUpWithMethod:(nullable NSString *)signUpMethodOrNil
success:(nullable NSNumber *)signUpSucceededOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log an Log In event to see users logging into your app in real-time, understand how many
* users are logging in with different methods and their success rate logging into your app.
*
* @param loginMethodOrNil The method by which a user logged in, e.g. email, Twitter or Digits.
* @param loginSucceededOrNil The ultimate success or failure of the login
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logLoginWithMethod:(nullable NSString *)loginMethodOrNil
success:(nullable NSNumber *)loginSucceededOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Share event to see users sharing from your app in real-time, letting you
* understand what content they're sharing from the type or genre down to the specific id.
*
* @param shareMethodOrNil The method by which a user shared, e.g. email, Twitter, SMS.
* @param contentNameOrNil The human readable name for this piece of content.
* @param contentTypeOrNil The type of content shared.
* @param contentIdOrNil The unique identifier for this piece of content. Useful for finding the top shared item.
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logShareWithMethod:(nullable NSString *)shareMethodOrNil
contentName:(nullable NSString *)contentNameOrNil
contentType:(nullable NSString *)contentTypeOrNil
contentId:(nullable NSString *)contentIdOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log an Invite Event to track how users are inviting other users into
* your application.
*
* @param inviteMethodOrNil The method of invitation, e.g. GameCenter, Twitter, email.
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logInviteWithMethod:(nullable NSString *)inviteMethodOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Purchase event to see your revenue in real-time, understand how many users are making purchases, see which
* items are most popular, and track plenty of other important purchase-related metrics.
*
* @param itemPriceOrNil The purchased item's price.
* @param currencyOrNil The ISO4217 currency code. Example: USD
* @param purchaseSucceededOrNil Was the purchase successful or unsuccessful
* @param itemNameOrNil The human-readable form of the item's name. Example:
* @param itemTypeOrNil The type, or genre of the item. Example: Song
* @param itemIdOrNil The machine-readable, unique item identifier Example: SKU
* @param customAttributesOrNil A dictionary of custom attributes to associate with this purchase.
*/
+ (void)logPurchaseWithPrice:(nullable NSDecimalNumber *)itemPriceOrNil
currency:(nullable NSString *)currencyOrNil
success:(nullable NSNumber *)purchaseSucceededOrNil
itemName:(nullable NSString *)itemNameOrNil
itemType:(nullable NSString *)itemTypeOrNil
itemId:(nullable NSString *)itemIdOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Level Start Event to track where users are in your game.
*
* @param levelNameOrNil The level name
* @param customAttributesOrNil A dictionary of custom attributes to associate with this level start event.
*/
+ (void)logLevelStart:(nullable NSString *)levelNameOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Level End event to track how users are completing levels in your game.
*
* @param levelNameOrNil The name of the level completed, E.G. "1" or "Training"
* @param scoreOrNil The score the user completed the level with.
* @param levelCompletedSuccesfullyOrNil A boolean representing whether or not the level was completed successfully.
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logLevelEnd:(nullable NSString *)levelNameOrNil
score:(nullable NSNumber *)scoreOrNil
success:(nullable NSNumber *)levelCompletedSuccesfullyOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log an Add to Cart event to see users adding items to a shopping cart in real-time, understand how
* many users start the purchase flow, see which items are most popular, and track plenty of other important
* purchase-related metrics.
*
* @param itemPriceOrNil The purchased item's price.
* @param currencyOrNil The ISO4217 currency code. Example: USD
* @param itemNameOrNil The human-readable form of the item's name. Example:
* @param itemTypeOrNil The type, or genre of the item. Example: Song
* @param itemIdOrNil The machine-readable, unique item identifier Example: SKU
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logAddToCartWithPrice:(nullable NSDecimalNumber *)itemPriceOrNil
currency:(nullable NSString *)currencyOrNil
itemName:(nullable NSString *)itemNameOrNil
itemType:(nullable NSString *)itemTypeOrNil
itemId:(nullable NSString *)itemIdOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Start Checkout event to see users moving through the purchase funnel in real-time, understand how many
* users are doing this and how much they're spending per checkout, and see how it related to other important
* purchase-related metrics.
*
* @param totalPriceOrNil The total price of the cart.
* @param currencyOrNil The ISO4217 currency code. Example: USD
* @param itemCountOrNil The number of items in the cart.
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logStartCheckoutWithPrice:(nullable NSDecimalNumber *)totalPriceOrNil
currency:(nullable NSString *)currencyOrNil
itemCount:(nullable NSNumber *)itemCountOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Rating event to see users rating content within your app in real-time and understand what
* content is most engaging, from the type or genre down to the specific id.
*
* @param ratingOrNil The integer rating given by the user.
* @param contentNameOrNil The human readable name for this piece of content.
* @param contentTypeOrNil The type of content shared.
* @param contentIdOrNil The unique identifier for this piece of content. Useful for finding the top shared item.
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logRating:(nullable NSNumber *)ratingOrNil
contentName:(nullable NSString *)contentNameOrNil
contentType:(nullable NSString *)contentTypeOrNil
contentId:(nullable NSString *)contentIdOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Content View event to see users viewing content within your app in real-time and
* understand what content is most engaging, from the type or genre down to the specific id.
*
* @param contentNameOrNil The human readable name for this piece of content.
* @param contentTypeOrNil The type of content shared.
* @param contentIdOrNil The unique identifier for this piece of content. Useful for finding the top shared item.
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logContentViewWithName:(nullable NSString *)contentNameOrNil
contentType:(nullable NSString *)contentTypeOrNil
contentId:(nullable NSString *)contentIdOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Search event allows you to see users searching within your app in real-time and understand
* exactly what they're searching for.
*
* @param queryOrNil The user's query.
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event.
*/
+ (void)logSearchWithQuery:(nullable NSString *)queryOrNil
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
/**
* Log a Custom Event to see user actions that are uniquely important for your app in real-time, to see how often
* they're performing these actions with breakdowns by different categories you add. Use a human-readable name for
* the name of the event, since this is how the event will appear in Answers.
*
* @param eventName The human-readable name for the event.
* @param customAttributesOrNil A dictionary of custom attributes to associate with this event. Attribute keys
* must be <code>NSString</code> and values must be <code>NSNumber</code> or <code>NSString</code>.
* @discussion How we treat <code>NSNumbers</code>:
* We will provide information about the distribution of values over time.
*
* How we treat <code>NSStrings</code>:
* NSStrings are used as categorical data, allowing comparison across different category values.
* Strings are limited to a maximum length of 100 characters, attributes over this length will be
* truncated.
*
* When tracking the Tweet views to better understand user engagement, sending the tweet's length
* and the type of media present in the tweet allows you to track how tweet length and the type of media influence
* engagement.
*/
+ (void)logCustomEventWithName:(NSString *)eventName
customAttributes:(nullable ANS_GENERIC_NSDICTIONARY(NSString *, id) *)customAttributesOrNil;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,33 +0,0 @@
//
// CLSAttributes.h
// Crashlytics
//
// Copyright (c) 2015 Crashlytics, Inc. All rights reserved.
//
#pragma once
#define CLS_DEPRECATED(x) __attribute__ ((deprecated(x)))
#if !__has_feature(nullability)
#define nonnull
#define nullable
#define _Nullable
#define _Nonnull
#endif
#ifndef NS_ASSUME_NONNULL_BEGIN
#define NS_ASSUME_NONNULL_BEGIN
#endif
#ifndef NS_ASSUME_NONNULL_END
#define NS_ASSUME_NONNULL_END
#endif
#if __has_feature(objc_generics)
#define CLS_GENERIC_NSARRAY(type) NSArray<type>
#define CLS_GENERIC_NSDICTIONARY(key_type,object_key) NSDictionary<key_type, object_key>
#else
#define CLS_GENERIC_NSARRAY(type) NSArray
#define CLS_GENERIC_NSDICTIONARY(key_type,object_key) NSDictionary
#endif

View File

@ -1,64 +0,0 @@
//
// CLSLogging.h
// Crashlytics
//
// Copyright (c) 2015 Crashlytics, Inc. All rights reserved.
//
#ifdef __OBJC__
#import "CLSAttributes.h"
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#endif
/**
*
* The CLS_LOG macro provides as easy way to gather more information in your log messages that are
* sent with your crash data. CLS_LOG prepends your custom log message with the function name and
* line number where the macro was used. If your app was built with the DEBUG preprocessor macro
* defined CLS_LOG uses the CLSNSLog function which forwards your log message to NSLog and CLSLog.
* If the DEBUG preprocessor macro is not defined CLS_LOG uses CLSLog only.
*
* Example output:
* -[AppDelegate login:] line 134 $ login start
*
* If you would like to change this macro, create a new header file, unset our define and then define
* your own version. Make sure this new header file is imported after the Crashlytics header file.
*
* #undef CLS_LOG
* #define CLS_LOG(__FORMAT__, ...) CLSNSLog...
*
**/
#ifdef __OBJC__
#ifdef DEBUG
#define CLS_LOG(__FORMAT__, ...) CLSNSLog((@"%s line %d $ " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
#else
#define CLS_LOG(__FORMAT__, ...) CLSLog((@"%s line %d $ " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
#endif
#endif
/**
*
* Add logging that will be sent with your crash data. This logging will not show up in the system.log
* and will only be visible in your Crashlytics dashboard.
*
**/
#ifdef __OBJC__
OBJC_EXTERN void CLSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2);
OBJC_EXTERN void CLSLogv(NSString *format, va_list ap) NS_FORMAT_FUNCTION(1,0);
/**
*
* Add logging that will be sent with your crash data. This logging will show up in the system.log
* and your Crashlytics dashboard. It is not recommended for Release builds.
*
**/
OBJC_EXTERN void CLSNSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2);
OBJC_EXTERN void CLSNSLogv(NSString *format, va_list ap) NS_FORMAT_FUNCTION(1,0);
NS_ASSUME_NONNULL_END
#endif

View File

@ -1,103 +0,0 @@
//
// CLSReport.h
// Crashlytics
//
// Copyright (c) 2015 Crashlytics, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "CLSAttributes.h"
NS_ASSUME_NONNULL_BEGIN
/**
* The CLSCrashReport protocol is deprecated. See the CLSReport class and the CrashyticsDelegate changes for details.
**/
@protocol CLSCrashReport <NSObject>
@property (nonatomic, copy, readonly) NSString *identifier;
@property (nonatomic, copy, readonly) NSDictionary *customKeys;
@property (nonatomic, copy, readonly) NSString *bundleVersion;
@property (nonatomic, copy, readonly) NSString *bundleShortVersionString;
@property (nonatomic, readonly, nullable) NSDate *crashedOnDate;
@property (nonatomic, copy, readonly) NSString *OSVersion;
@property (nonatomic, copy, readonly) NSString *OSBuildVersion;
@end
/**
* The CLSReport exposes an interface to the phsyical report that Crashlytics has created. You can
* use this class to get information about the event, and can also set some values after the
* event has occurred.
**/
@interface CLSReport : NSObject <CLSCrashReport>
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
/**
* Returns the session identifier for the report.
**/
@property (nonatomic, copy, readonly) NSString *identifier;
/**
* Returns the custom key value data for the report.
**/
@property (nonatomic, copy, readonly) NSDictionary *customKeys;
/**
* Returns the CFBundleVersion of the application that generated the report.
**/
@property (nonatomic, copy, readonly) NSString *bundleVersion;
/**
* Returns the CFBundleShortVersionString of the application that generated the report.
**/
@property (nonatomic, copy, readonly) NSString *bundleShortVersionString;
/**
* Returns the date that the report was created.
**/
@property (nonatomic, copy, readonly) NSDate *dateCreated;
/**
* Returns the os version that the application crashed on.
**/
@property (nonatomic, copy, readonly) NSString *OSVersion;
/**
* Returns the os build version that the application crashed on.
**/
@property (nonatomic, copy, readonly) NSString *OSBuildVersion;
/**
* Returns YES if the report contains any crash information, otherwise returns NO.
**/
@property (nonatomic, assign, readonly) BOOL isCrash;
/**
* You can use this method to set, after the event, additional custom keys. The rules
* and semantics for this method are the same as those documented in Crashlytics.h. Be aware
* that the maximum size and count of custom keys is still enforced, and you can overwrite keys
* and/or cause excess keys to be deleted by using this method.
**/
- (void)setObjectValue:(nullable id)value forKey:(NSString *)key;
/**
* Record an application-specific user identifier. See Crashlytics.h for details.
**/
@property (nonatomic, copy, nullable) NSString * userIdentifier;
/**
* Record a user name. See Crashlytics.h for details.
**/
@property (nonatomic, copy, nullable) NSString * userName;
/**
* Record a user email. See Crashlytics.h for details.
**/
@property (nonatomic, copy, nullable) NSString * userEmail;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,38 +0,0 @@
//
// CLSStackFrame.h
// Crashlytics
//
// Copyright 2015 Crashlytics, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "CLSAttributes.h"
NS_ASSUME_NONNULL_BEGIN
/**
*
* This class is used in conjunction with -[Crashlytics recordCustomExceptionName:reason:frameArray:] to
* record information about non-ObjC/C++ exceptions. All information included here will be displayed
* in the Crashlytics UI, and can influence crash grouping. Be particularly careful with the use of the
* address property. If set, Crashlytics will attempt symbolication and could overwrite other properities
* in the process.
*
**/
@interface CLSStackFrame : NSObject
+ (instancetype)stackFrame;
+ (instancetype)stackFrameWithAddress:(NSUInteger)address;
+ (instancetype)stackFrameWithSymbol:(NSString *)symbol;
@property (nonatomic, copy, nullable) NSString *symbol;
@property (nonatomic, copy, nullable) NSString *rawSymbol;
@property (nonatomic, copy, nullable) NSString *library;
@property (nonatomic, copy, nullable) NSString *fileName;
@property (nonatomic, assign) uint32_t lineNumber;
@property (nonatomic, assign) uint64_t offset;
@property (nonatomic, assign) uint64_t address;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,288 +0,0 @@
//
// Crashlytics.h
// Crashlytics
//
// Copyright (c) 2015 Crashlytics, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "CLSAttributes.h"
#import "CLSLogging.h"
#import "CLSReport.h"
#import "CLSStackFrame.h"
#import "Answers.h"
NS_ASSUME_NONNULL_BEGIN
@protocol CrashlyticsDelegate;
/**
* Crashlytics. Handles configuration and initialization of Crashlytics.
*
* Note: The Crashlytics class cannot be subclassed. If this is causing you pain for
* testing, we suggest using either a wrapper class or a protocol extension.
*/
@interface Crashlytics : NSObject
@property (nonatomic, readonly, copy) NSString *APIKey;
@property (nonatomic, readonly, copy) NSString *version;
@property (nonatomic, assign) BOOL debugMode;
/**
*
* The delegate can be used to influence decisions on reporting and behavior, as well as reacting
* to previous crashes.
*
* Make certain that the delegate is setup before starting Crashlytics with startWithAPIKey:... or
* via +[Fabric with:...]. Failure to do will result in missing any delegate callbacks that occur
* synchronously during start.
*
**/
@property (nonatomic, assign, nullable) id <CrashlyticsDelegate> delegate;
/**
* The recommended way to install Crashlytics into your application is to place a call to +startWithAPIKey:
* in your -application:didFinishLaunchingWithOptions: or -applicationDidFinishLaunching:
* method.
*
* Note: Starting with 3.0, the submission process has been significantly improved. The delay parameter
* is no longer required to throttle submissions on launch, performance will be great without it.
*
* @param apiKey The Crashlytics API Key for this app
*
* @return The singleton Crashlytics instance
*/
+ (Crashlytics *)startWithAPIKey:(NSString *)apiKey;
+ (Crashlytics *)startWithAPIKey:(NSString *)apiKey afterDelay:(NSTimeInterval)delay CLS_DEPRECATED("Crashlytics no longer needs or uses the delay parameter. Please use +startWithAPIKey: instead.");
/**
* If you need the functionality provided by the CrashlyticsDelegate protocol, you can use
* these convenience methods to activate the framework and set the delegate in one call.
*
* @param apiKey The Crashlytics API Key for this app
* @param delegate A delegate object which conforms to CrashlyticsDelegate.
*
* @return The singleton Crashlytics instance
*/
+ (Crashlytics *)startWithAPIKey:(NSString *)apiKey delegate:(nullable id<CrashlyticsDelegate>)delegate;
+ (Crashlytics *)startWithAPIKey:(NSString *)apiKey delegate:(nullable id<CrashlyticsDelegate>)delegate afterDelay:(NSTimeInterval)delay CLS_DEPRECATED("Crashlytics no longer needs or uses the delay parameter. Please use +startWithAPIKey:delegate: instead.");
/**
* Access the singleton Crashlytics instance.
*
* @return The singleton Crashlytics instance
*/
+ (Crashlytics *)sharedInstance;
/**
* The easiest way to cause a crash - great for testing!
*/
- (void)crash;
/**
* The easiest way to cause a crash with an exception - great for testing.
*/
- (void)throwException;
/**
* Specify a user identifier which will be visible in the Crashlytics UI.
*
* Many of our customers have requested the ability to tie crashes to specific end-users of their
* application in order to facilitate responses to support requests or permit the ability to reach
* out for more information. We allow you to specify up to three separate values for display within
* the Crashlytics UI - but please be mindful of your end-user's privacy.
*
* We recommend specifying a user identifier - an arbitrary string that ties an end-user to a record
* in your system. This could be a database id, hash, or other value that is meaningless to a
* third-party observer but can be indexed and queried by you.
*
* Optionally, you may also specify the end-user's name or username, as well as email address if you
* do not have a system that works well with obscured identifiers.
*
* Pursuant to our EULA, this data is transferred securely throughout our system and we will not
* disseminate end-user data unless required to by law. That said, if you choose to provide end-user
* contact information, we strongly recommend that you disclose this in your application's privacy
* policy. Data privacy is of our utmost concern.
*
* @param identifier An arbitrary user identifier string which ties an end-user to a record in your system.
*/
- (void)setUserIdentifier:(nullable NSString *)identifier;
/**
* Specify a user name which will be visible in the Crashlytics UI.
* Please be mindful of your end-user's privacy and see if setUserIdentifier: can fulfil your needs.
* @see setUserIdentifier:
*
* @param name An end user's name.
*/
- (void)setUserName:(nullable NSString *)name;
/**
* Specify a user email which will be visible in the Crashlytics UI.
* Please be mindful of your end-user's privacy and see if setUserIdentifier: can fulfil your needs.
*
* @see setUserIdentifier:
*
* @param email An end user's email address.
*/
- (void)setUserEmail:(nullable NSString *)email;
+ (void)setUserIdentifier:(nullable NSString *)identifier CLS_DEPRECATED("Please access this method via +sharedInstance");
+ (void)setUserName:(nullable NSString *)name CLS_DEPRECATED("Please access this method via +sharedInstance");
+ (void)setUserEmail:(nullable NSString *)email CLS_DEPRECATED("Please access this method via +sharedInstance");
/**
* Set a value for a for a key to be associated with your crash data which will be visible in the Crashlytics UI.
* When setting an object value, the object is converted to a string. This is typically done by calling
* -[NSObject description].
*
* @param value The object to be associated with the key
* @param key The key with which to associate the value
*/
- (void)setObjectValue:(nullable id)value forKey:(NSString *)key;
/**
* Set an int value for a key to be associated with your crash data which will be visible in the Crashlytics UI.
*
* @param value The integer value to be set
* @param key The key with which to associate the value
*/
- (void)setIntValue:(int)value forKey:(NSString *)key;
/**
* Set an BOOL value for a key to be associated with your crash data which will be visible in the Crashlytics UI.
*
* @param value The BOOL value to be set
* @param key The key with which to associate the value
*/
- (void)setBoolValue:(BOOL)value forKey:(NSString *)key;
/**
* Set an float value for a key to be associated with your crash data which will be visible in the Crashlytics UI.
*
* @param value The float value to be set
* @param key The key with which to associate the value
*/
- (void)setFloatValue:(float)value forKey:(NSString *)key;
+ (void)setObjectValue:(nullable id)value forKey:(NSString *)key CLS_DEPRECATED("Please access this method via +sharedInstance");
+ (void)setIntValue:(int)value forKey:(NSString *)key CLS_DEPRECATED("Please access this method via +sharedInstance");
+ (void)setBoolValue:(BOOL)value forKey:(NSString *)key CLS_DEPRECATED("Please access this method via +sharedInstance");
+ (void)setFloatValue:(float)value forKey:(NSString *)key CLS_DEPRECATED("Please access this method via +sharedInstance");
/**
* This method can be used to record a single exception structure in a report. This is particularly useful
* when your code interacts with non-native languages like Lua, C#, or Javascript. This call can be
* expensive and should only be used shortly before process termination. This API is not intended be to used
* to log NSException objects. All safely-reportable NSExceptions are automatically captured by
* Crashlytics.
*
* @param name The name of the custom exception
* @param reason The reason this exception occurred
* @param frameArray An array of CLSStackFrame objects
*/
- (void)recordCustomExceptionName:(NSString *)name reason:(nullable NSString *)reason frameArray:(CLS_GENERIC_NSARRAY(CLSStackFrame *) *)frameArray;
/**
*
* This allows you to record a non-fatal event, described by an NSError object. These events will be grouped and
* displayed similarly to crashes. Keep in mind that this method can be expensive. Also, the total number of
* NSErrors that can be recorded during your app's life-cycle is limited by a fixed-size circular buffer. If the
* buffer is overrun, the oldest data is dropped. Errors are relayed to Crashlytics on a subsequent launch
* of your application.
*
* You can also use the -recordError:withAdditionalUserInfo: to include additional context not represented
* by the NSError instance itself.
*
**/
- (void)recordError:(NSError *)error;
- (void)recordError:(NSError *)error withAdditionalUserInfo:(nullable CLS_GENERIC_NSDICTIONARY(NSString *, id) *)userInfo;
- (void)logEvent:(NSString *)eventName CLS_DEPRECATED("Please refer to Answers +logCustomEventWithName:");
- (void)logEvent:(NSString *)eventName attributes:(nullable NSDictionary *) attributes CLS_DEPRECATED("Please refer to Answers +logCustomEventWithName:");
+ (void)logEvent:(NSString *)eventName CLS_DEPRECATED("Please refer to Answers +logCustomEventWithName:");
+ (void)logEvent:(NSString *)eventName attributes:(nullable NSDictionary *) attributes CLS_DEPRECATED("Please refer to Answers +logCustomEventWithName:");
@end
/**
*
* The CrashlyticsDelegate protocol provides a mechanism for your application to take
* action on events that occur in the Crashlytics crash reporting system. You can make
* use of these calls by assigning an object to the Crashlytics' delegate property directly,
* or through the convenience +startWithAPIKey:delegate: method.
*
*/
@protocol CrashlyticsDelegate <NSObject>
@optional
- (void)crashlyticsDidDetectCrashDuringPreviousExecution:(Crashlytics *)crashlytics CLS_DEPRECATED("Please refer to -crashlyticsDidDetectReportForLastExecution:");
- (void)crashlytics:(Crashlytics *)crashlytics didDetectCrashDuringPreviousExecution:(id <CLSCrashReport>)crash CLS_DEPRECATED("Please refer to -crashlyticsDidDetectReportForLastExecution:");
/**
*
* Called when a Crashlytics instance has determined that the last execution of the
* application resulted in a saved report. This is called synchronously on Crashlytics
* initialization. Your delegate must invoke the completionHandler, but does not need to do so
* synchronously, or even on the main thread. Invoking completionHandler with NO will cause the
* detected report to be deleted and not submitted to Crashlytics. This is useful for
* implementing permission prompts, or other more-complex forms of logic around submitting crashes.
*
* Instead of using this method, you should try to make use of -crashlyticsDidDetectReportForLastExecution:
* if you can.
*
* @warning Failure to invoke the completionHandler will prevent submissions from being reported. Watch out.
*
* @warning Just implementing this delegate method will disable all forms of synchronous report submission. This can
* impact the reliability of reporting crashes very early in application launch.
*
* @param report The CLSReport object representing the last detected report
* @param completionHandler The completion handler to call when your logic has completed.
*
*/
- (void)crashlyticsDidDetectReportForLastExecution:(CLSReport *)report completionHandler:(void (^)(BOOL submit))completionHandler;
/**
*
* Called when a Crashlytics instance has determined that the last execution of the
* application resulted in a saved report. This method differs from
* -crashlyticsDidDetectReportForLastExecution:completionHandler: in three important ways:
*
* - it is not called synchronously during initialization
* - it does not give you the ability to prevent the report from being submitted
* - the report object itself is immutable
*
* Thanks to these limitations, making use of this method does not impact reporting
* reliabilty in any way.
*
* @param report The read-only CLSReport object representing the last detected report
*
*/
- (void)crashlyticsDidDetectReportForLastExecution:(CLSReport *)report;
/**
* If your app is running on an OS that supports it (OS X 10.9+, iOS 7.0+), Crashlytics will submit
* most reports using out-of-process background networking operations. This results in a significant
* improvement in reliability of reporting, as well as power and performance wins for your users.
* If you don't want this functionality, you can disable by returning NO from this method.
*
* @warning Background submission is not supported for extensions on iOS or OS X.
*
* @param crashlytics The Crashlytics singleton instance
*
* @return Return NO if you don't want out-of-process background network operations.
*
*/
- (BOOL)crashlyticsCanUseBackgroundSessions:(Crashlytics *)crashlytics;
@end
/**
* `CrashlyticsKit` can be used as a parameter to `[Fabric with:@[CrashlyticsKit]];` in Objective-C. In Swift, use Crashlytics.sharedInstance()
*/
#define CrashlyticsKit [Crashlytics sharedInstance]
NS_ASSUME_NONNULL_END

Binary file not shown.

View File

@ -1,14 +0,0 @@
framework module Crashlytics {
header "Crashlytics.h"
header "Answers.h"
header "ANSCompatibility.h"
header "CLSLogging.h"
header "CLSReport.h"
header "CLSStackFrame.h"
header "CLSAttributes.h"
export *
link "z"
link "c++"
}

View File

@ -1,73 +0,0 @@
#!/bin/sh
# run
#
# Copyright (c) 2015 Crashlytics. All rights reserved.
#
#
# This script is meant to be run as a Run Script in the "Build Phases" section
# of your Xcode project. It sends debug symbols to symbolicate stacktraces,
# sends build events to track versions, and onboard apps for Crashlytics.
#
# This script calls upload-symbols twice:
#
# 1) First it calls upload-symbols synchronously in "validation" mode. If the
# script finds issues with the build environment, it will report errors to Xcode.
# In validation mode it exits before doing any time consuming work.
#
# 2) Then it calls upload-symbols in the background to actually send the build
# event and upload symbols. It does this in the background so that it doesn't
# slow down your builds. If an error happens here, you won't see it in Xcode.
#
# You can find the output for the background execution in Console.app, by
# searching for "upload-symbols".
#
# If you want verbose output, you can pass the --debug flag to this script
#
# Figure out where we're being called from
DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
# If the first argument is specified without a dash, treat it as the Fabric API
# Key and add it as an argument
if [ -z "$1" ] || [[ $1 == -* ]]; then
API_KEY_ARG=""
else
API_KEY_ARG="-a $1"; shift
fi
# If a second argument is specified without a dash, treat it as the Build Secret
# and add it as an argument
if [ -z "$1" ] || [[ $1 == -* ]]; then
BUILD_SECRET_ARG=""
else
BUILD_SECRET_ARG="-bs $1"; shift
fi
# Build up the arguments list, passing through any flags added after the
# API Key and Build Secret
ARGUMENTS="$API_KEY_ARG $BUILD_SECRET_ARG $@"
VALIDATE_ARGUMENTS="$ARGUMENTS --build-phase --validate"
UPLOAD_ARGUMENTS="$ARGUMENTS --build-phase"
# Quote the path to handle folders with special characters
COMMAND_PATH="\"$DIR/upload-symbols\" "
# Ensure params are as expected, run in sync mode to validate,
# and cause a build error if validation fails
eval $COMMAND_PATH$VALIDATE_ARGUMENTS
return_code=$?
if [[ $return_code != 0 ]]; then
exit $return_code
fi
# Verification passed, convert and upload cSYMs in the background to prevent
# build delays
#
# Note: Validation is performed again at this step before upload
#
# Note: Output can still be found in Console.app, by searching for
# "upload-symbols"
#
eval $COMMAND_PATH$UPLOAD_ARGUMENTS > /dev/null 2>&1 &

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1 +0,0 @@
We've now combined all our supported platforms into a single podspec. As a result, we moved our run script to a new location for Cocoapods projects: ${PODS_ROOT}/Fabric/run. To avoid breaking builds that reference the old location of the run script, we've placed this dummy script that calls to the correct location, while providing a helpful warning in Xcode if it is invoked. This bridge for backwards compatibility will be removed in a future release, so please heed the warning!

View File

@ -1,6 +0,0 @@
if [[ -z $PODS_ROOT ]]; then
echo "error: The run binary delivered by cocoapods is in a new location, under '$"{"PODS_ROOT"}"/Fabric/run'. This script was put in place for backwards compatibility, but it relies on PODS_ROOT, which does not have a value in your current setup. Please update the path to the run binary to fix this issue."
else
echo "warning: The run script is now located at '$"{"PODS_ROOT"}"/Fabric/run'. To remove this warning, update your Run Script Build Phase to point to this new location."
sh "${PODS_ROOT}/Fabric/run" "$@"
fi

View File

@ -1,27 +0,0 @@
# Fabric
## Overview
[Fabric](https://get.fabric.io) provides developers with the tools they need to build the best apps. Developed and maintained by Google and the team that built Crashlytics.
For a full list of SDKs provided through Fabric visit [https://fabric.io/kits](https://fabric.io/kits).
To follow the migration to Firebase, check out the [Fabric Roadmap](https://get.fabric.io/roadmap).
## Setup
Fabric is a dependency for the Crashlytics SDK. To start using Crashlytics, there are two options:
1) The recommended way is to go to the [Firebase Crashlytics Docs](https://firebase.google.com/docs/crashlytics/get-started?platform=ios) and follow the directions there.
2) If you aren't using Firebase yet, go to [Fabric Kits](https://fabric.io/kits), and follow the directions for Crashlytics.
## Resources
* [Documentation](https://docs.fabric.io/)
* [Forums](https://stackoverflow.com/questions/tagged/google-fabric)
* [Website](https://get.fabric.io)
* Follow us on Twitter: [@fabric](https://twitter.com/fabric)

Binary file not shown.

View File

@ -1,51 +0,0 @@
//
// FABAttributes.h
// Fabric
//
// Copyright (C) 2015 Twitter, Inc.
//
// 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.
//
#pragma once
#define FAB_UNAVAILABLE(x) __attribute__((unavailable(x)))
#if !__has_feature(nullability)
#define nonnull
#define nullable
#define _Nullable
#define _Nonnull
#endif
#ifndef NS_ASSUME_NONNULL_BEGIN
#define NS_ASSUME_NONNULL_BEGIN
#endif
#ifndef NS_ASSUME_NONNULL_END
#define NS_ASSUME_NONNULL_END
#endif
/**
* The following macros are defined here to provide
* backwards compatability. If you are still using
* them you should migrate to the native nullability
* macros.
*/
#define fab_nullable nullable
#define fab_nonnull nonnull
#define FAB_NONNULL __fab_nonnull
#define FAB_NULLABLE __fab_nullable
#define FAB_START_NONNULL NS_ASSUME_NONNULL_BEGIN
#define FAB_END_NONNULL NS_ASSUME_NONNULL_END

View File

@ -1,82 +0,0 @@
//
// Fabric.h
// Fabric
//
// Copyright (C) 2015 Twitter, Inc.
//
// 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.
//
#import <Foundation/Foundation.h>
#import "FABAttributes.h"
NS_ASSUME_NONNULL_BEGIN
#if TARGET_OS_IPHONE
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 60000
#error "Fabric's minimum iOS version is 6.0"
#endif
#else
#if __MAC_OS_X_VERSION_MIN_REQUIRED < 1070
#error "Fabric's minimum OS X version is 10.7"
#endif
#endif
/**
* Fabric Base. Coordinates configuration and starts all provided kits.
*/
@interface Fabric : NSObject
/**
* Initialize Fabric and all provided kits. Call this method within your App Delegate's `application:didFinishLaunchingWithOptions:` and provide the kits you wish to use.
*
* For example, in Objective-C:
*
* `[Fabric with:@[[Crashlytics class], [Twitter class], [Digits class], [MoPub class]]];`
*
* Swift:
*
* `Fabric.with([Crashlytics.self(), Twitter.self(), Digits.self(), MoPub.self()])`
*
* Only the first call to this method is honored. Subsequent calls are no-ops.
*
* @param kitClasses An array of kit Class objects
*
* @return Returns the shared Fabric instance. In most cases this can be ignored.
*/
+ (instancetype)with:(NSArray *)kitClasses;
/**
* Returns the Fabric singleton object.
*/
+ (instancetype)sharedSDK;
/**
* This BOOL enables or disables debug logging, such as kit version information. The default value is NO.
*/
@property (nonatomic, assign) BOOL debug;
/**
* Unavailable. Use `+sharedSDK` to retrieve the shared Fabric instance.
*/
- (id)init FAB_UNAVAILABLE("Use +sharedSDK to retrieve the shared Fabric instance.");
/**
* Unavailable. Use `+sharedSDK` to retrieve the shared Fabric instance.
*/
+ (instancetype)new FAB_UNAVAILABLE("Use +sharedSDK to retrieve the shared Fabric instance.");
@end
NS_ASSUME_NONNULL_END

Binary file not shown.

View File

@ -1,6 +0,0 @@
framework module Fabric {
umbrella header "Fabric.h"
export *
module * { export * }
}

View File

@ -1,73 +0,0 @@
#!/bin/sh
# run
#
# Copyright (c) 2015 Crashlytics. All rights reserved.
#
#
# This script is meant to be run as a Run Script in the "Build Phases" section
# of your Xcode project. It sends debug symbols to symbolicate stacktraces,
# sends build events to track versions, and onboard apps for Crashlytics.
#
# This script calls upload-symbols twice:
#
# 1) First it calls upload-symbols synchronously in "validation" mode. If the
# script finds issues with the build environment, it will report errors to Xcode.
# In validation mode it exits before doing any time consuming work.
#
# 2) Then it calls upload-symbols in the background to actually send the build
# event and upload symbols. It does this in the background so that it doesn't
# slow down your builds. If an error happens here, you won't see it in Xcode.
#
# You can find the output for the background execution in Console.app, by
# searching for "upload-symbols".
#
# If you want verbose output, you can pass the --debug flag to this script
#
# Figure out where we're being called from
DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
# If the first argument is specified without a dash, treat it as the Fabric API
# Key and add it as an argument
if [ -z "$1" ] || [[ $1 == -* ]]; then
API_KEY_ARG=""
else
API_KEY_ARG="-a $1"; shift
fi
# If a second argument is specified without a dash, treat it as the Build Secret
# and add it as an argument
if [ -z "$1" ] || [[ $1 == -* ]]; then
BUILD_SECRET_ARG=""
else
BUILD_SECRET_ARG="-bs $1"; shift
fi
# Build up the arguments list, passing through any flags added after the
# API Key and Build Secret
ARGUMENTS="$API_KEY_ARG $BUILD_SECRET_ARG $@"
VALIDATE_ARGUMENTS="$ARGUMENTS --build-phase --validate"
UPLOAD_ARGUMENTS="$ARGUMENTS --build-phase"
# Quote the path to handle folders with special characters
COMMAND_PATH="\"$DIR/upload-symbols\" "
# Ensure params are as expected, run in sync mode to validate,
# and cause a build error if validation fails
eval $COMMAND_PATH$VALIDATE_ARGUMENTS
return_code=$?
if [[ $return_code != 0 ]]; then
exit $return_code
fi
# Verification passed, convert and upload cSYMs in the background to prevent
# build delays
#
# Note: Validation is performed again at this step before upload
#
# Note: Output can still be found in Console.app, by searching for
# "upload-symbols"
#
eval $COMMAND_PATH$UPLOAD_ARGUMENTS > /dev/null 2>&1 &

Binary file not shown.

73
ios/Pods/Fabric/run generated
View File

@ -1,73 +0,0 @@
#!/bin/sh
# run
#
# Copyright (c) 2015 Crashlytics. All rights reserved.
#
#
# This script is meant to be run as a Run Script in the "Build Phases" section
# of your Xcode project. It sends debug symbols to symbolicate stacktraces,
# sends build events to track versions, and onboard apps for Crashlytics.
#
# This script calls upload-symbols twice:
#
# 1) First it calls upload-symbols synchronously in "validation" mode. If the
# script finds issues with the build environment, it will report errors to Xcode.
# In validation mode it exits before doing any time consuming work.
#
# 2) Then it calls upload-symbols in the background to actually send the build
# event and upload symbols. It does this in the background so that it doesn't
# slow down your builds. If an error happens here, you won't see it in Xcode.
#
# You can find the output for the background execution in Console.app, by
# searching for "upload-symbols".
#
# If you want verbose output, you can pass the --debug flag to this script
#
# Figure out where we're being called from
DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
# If the first argument is specified without a dash, treat it as the Fabric API
# Key and add it as an argument
if [ -z "$1" ] || [[ $1 == -* ]]; then
API_KEY_ARG=""
else
API_KEY_ARG="-a $1"; shift
fi
# If a second argument is specified without a dash, treat it as the Build Secret
# and add it as an argument
if [ -z "$1" ] || [[ $1 == -* ]]; then
BUILD_SECRET_ARG=""
else
BUILD_SECRET_ARG="-bs $1"; shift
fi
# Build up the arguments list, passing through any flags added after the
# API Key and Build Secret
ARGUMENTS="$API_KEY_ARG $BUILD_SECRET_ARG $@"
VALIDATE_ARGUMENTS="$ARGUMENTS --build-phase --validate"
UPLOAD_ARGUMENTS="$ARGUMENTS --build-phase"
# Quote the path to handle folders with special characters
COMMAND_PATH="\"$DIR/upload-symbols\" "
# Ensure params are as expected, run in sync mode to validate,
# and cause a build error if validation fails
eval $COMMAND_PATH$VALIDATE_ARGUMENTS
return_code=$?
if [[ $return_code != 0 ]]; then
exit $return_code
fi
# Verification passed, convert and upload cSYMs in the background to prevent
# build delays
#
# Note: Validation is performed again at this step before upload
#
# Note: Output can still be found in Console.app, by searching for
# "upload-symbols"
#
eval $COMMAND_PATH$UPLOAD_ARGUMENTS > /dev/null 2>&1 &

Binary file not shown.

View File

@ -12,39 +12,26 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// The module qualified imports are for CocoaPods and the simple file names
// for Swift Package Manager.
#import <FirebaseCore/FirebaseCore.h>
#if !defined(__has_include)
#error "Firebase.h won't import anything if your compiler doesn't support __has_include. Please \
import the headers individually."
#else
#if __has_include(<FirebaseCore/FirebaseCore.h>)
#import <FirebaseCore/FirebaseCore.h>
#elif __has_include("FirebaseCore.h")
#import "FirebaseCore.h"
#endif
#if __has_include(<FirebaseAnalytics/FirebaseAnalytics.h>)
#import <FirebaseAnalytics/FirebaseAnalytics.h>
#endif
#if __has_include(<FirebaseAuth/FirebaseAuth.h>)
#import <FirebaseAuth/FirebaseAuth.h>
#elif __has_include("FirebaseAuth.h")
#import "FirebaseAuth.h"
#endif
#if __has_include(<FirebaseCrashlytics/FirebaseCrashlytics.h>)
#import <FirebaseCrashlytics/FirebaseCrashlytics.h>
#elif __has_include("FirebaseCrashlytics.h")
#import "FirebaseCrashlytics.h"
#endif
#if __has_include(<FirebaseDatabase/FirebaseDatabase.h>)
#import <FirebaseDatabase/FirebaseDatabase.h>
#elif __has_include("FirebaseDatabase.h")
#import "FirebaseDatabase.h"
#endif
#if __has_include(<FirebaseDynamicLinks/FirebaseDynamicLinks.h>)
@ -56,20 +43,14 @@
Firebase Dynamic Links works as intended."
#endif // #ifndef FIREBASE_ANALYTICS_SUPPRESS_WARNING
#endif
#elif __has_include("FirebaseDynamicLinks.h")
#import "FirebaseDynamicLinks.h"
#endif
#if __has_include(<FirebaseFirestore/FirebaseFirestore.h>)
#import <FirebaseFirestore/FirebaseFirestore.h>
#elif __has_include("FirebaseFirestore.h")
#import "FirebaseFirestore.h"
#endif
#if __has_include(<FirebaseFunctions/FirebaseFunctions.h>)
#import <FirebaseFunctions/FirebaseFunctions.h>
#elif __has_include("FirebaseFunctions.h")
#import "FirebaseFunctions.h"
#endif
#if __has_include(<FirebaseInAppMessaging/FirebaseInAppMessaging.h>)
@ -81,8 +62,6 @@ Firebase Dynamic Links works as intended."
Firebase In App Messaging works as intended."
#endif // #ifndef FIREBASE_ANALYTICS_SUPPRESS_WARNING
#endif
#elif __has_include("FirebaseInAppMessaging.h")
#import "FirebaseInAppMessaging.h"
#endif
#if __has_include(<FirebaseInstanceID/FirebaseInstanceID.h>)
@ -98,9 +77,7 @@ Firebase In App Messaging works as intended."
Firebase Messaging works as intended."
#endif // #ifndef FIREBASE_ANALYTICS_SUPPRESS_WARNING
#endif
#elif __has_include("FirebaseMessaging.h")
#import "FirebaseMessaging.h"
#endif
#endif
#if __has_include(<FirebaseMLCommon/FirebaseMLCommon.h>)
#import <FirebaseMLCommon/FirebaseMLCommon.h>
@ -174,14 +151,10 @@ Firebase Performance works as intended."
Firebase Remote Config works as intended."
#endif // #ifndef FIREBASE_ANALYTICS_SUPPRESS_WARNING
#endif
#elif __has_include("FirebaseRemoteConfig.h")
#import "FirebaseRemoteConfig.h"
#endif
#if __has_include(<FirebaseStorage/FirebaseStorage.h>)
#import <FirebaseStorage/FirebaseStorage.h>
#elif __has_include("FirebaseStorage.h")
#import "FirebaseStorage.h"
#endif
#if __has_include(<GoogleMobileAds/GoogleMobileAds.h>)

View File

@ -140,13 +140,21 @@ To ensure that the code is formatted consistently, run the script
before creating a PR.
Travis will verify that any code changes are done in a style compliant way. Install
`clang-format` and `swiftformat`:
`clang-format` and `swiftformat`.
These commands will get the right versions:
```
brew install clang-format
brew install swiftformat
brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/c6f1cbd/Formula/clang-format.rb
brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/c13eda8/Formula/swiftformat.rb
```
Note: if you already have a newer version of these installed you may need to
`brew switch` to this version.
To update this section, find the versions of clang-format and swiftformat.rb to
match the versions in the CI failure logs
[here](https://github.com/Homebrew/homebrew-core/tree/master/Formula).
### Running Unit Tests
Select a scheme and press Command-u to build a component and run its unit tests.
@ -169,7 +177,12 @@ files without real values, but can be replaced with real plist files. To get you
2. Create a new Firebase project, if you don't already have one
3. For each sample app you want to test, create a new Firebase app with the sample app's bundle
identifier (e.g. `com.google.Database-Example`)
4. Download the resulting `GoogleService-Info.plist` and add it to the Xcode project.
4. Download the resulting `GoogleService-Info.plist` and replace the appropriate dummy plist file
(e.g. in [Example/Database/App/](Example/Database/App/));
Some sample apps like Firebase Messaging ([Example/Messaging/App](Example/Messaging/App)) require
special Apple capabilities, and you will have to change the sample app to use a unique bundle
identifier that you can control in your own Apple Developer account.
## Specific Component Instructions
See the sections below for any special instructions for those components.
@ -189,7 +202,7 @@ To run against a local emulator instance, invoke `./scripts/run_database_emulato
running the integration test.
To run against a production instance, provide a valid GoogleServices-Info.plist and copy it to
`FirebaseDatabase/Tests/Resources/GoogleService-Info.plist`. Your Security Rule must be set to
`Example/Database/App/GoogleService-Info.plist`. Your Security Rule must be set to
[public](https://firebase.google.com/docs/database/security/quickstart) while your tests are
running.

View File

@ -35,7 +35,7 @@
#import "FirebaseCore/Sources/Private/FIRLogger.h"
#import "FirebaseCore/Sources/Private/FIROptionsInternal.h"
#import "GoogleUtilities/Environment/Private/GULAppEnvironmentUtil.h"
#import <GoogleUtilities/GULAppEnvironmentUtil.h>
#import <objc/runtime.h>

View File

@ -14,7 +14,7 @@
#import "FirebaseCore/Sources/FIRBundleUtil.h"
#import "GoogleUtilities/Environment/Private/GULAppEnvironmentUtil.h"
#import <GoogleUtilities/GULAppEnvironmentUtil.h>
@implementation FIRBundleUtil

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
#import "FirebaseCore/Sources/Public/FIRConfiguration.h"
#import "FIRConfiguration.h"
@class FIRAnalyticsConfiguration;

View File

@ -13,8 +13,8 @@
// limitations under the License.
#import "FirebaseCore/Sources/Private/FIRHeartbeatInfo.h"
#import "GoogleUtilities/Environment/Private/GULHeartbeatDateStorage.h"
#import "GoogleUtilities/Logger/Private/GULLogger.h"
#import <GoogleUtilities/GULHeartbeatDateStorage.h>
#import <GoogleUtilities/GULLogger.h>
const static long secondsInDay = 86400;
@implementation FIRHeartbeatInfo : NSObject

View File

@ -14,9 +14,9 @@
#import "FirebaseCore/Sources/Private/FIRLogger.h"
#import <GoogleUtilities/GULAppEnvironmentUtil.h>
#import <GoogleUtilities/GULLogger.h>
#import "FirebaseCore/Sources/Public/FIRLoggerLevel.h"
#import "GoogleUtilities/Environment/Private/GULAppEnvironmentUtil.h"
#import "GoogleUtilities/Logger/Private/GULLogger.h"
#import "FirebaseCore/Sources/FIRVersion.h"

View File

@ -90,40 +90,40 @@ NSString *const kFIRExceptionBadModification =
static FIROptions *sDefaultOptions = nil;
static NSDictionary *sDefaultOptionsDictionary = nil;
static dispatch_once_t sDefaultOptionsOnceToken;
static dispatch_once_t sDefaultOptionsDictionaryOnceToken;
#pragma mark - Public only for internal class methods
+ (FIROptions *)defaultOptions {
dispatch_once(&sDefaultOptionsOnceToken, ^{
NSDictionary *defaultOptionsDictionary = [self defaultOptionsDictionary];
if (defaultOptionsDictionary != nil) {
sDefaultOptions =
[[FIROptions alloc] initInternalWithOptionsDictionary:defaultOptionsDictionary];
}
});
if (sDefaultOptions != nil) {
return sDefaultOptions;
}
NSDictionary *defaultOptionsDictionary = [self defaultOptionsDictionary];
if (defaultOptionsDictionary == nil) {
return nil;
}
sDefaultOptions = [[FIROptions alloc] initInternalWithOptionsDictionary:defaultOptionsDictionary];
return sDefaultOptions;
}
#pragma mark - Private class methods
+ (NSDictionary *)defaultOptionsDictionary {
dispatch_once(&sDefaultOptionsDictionaryOnceToken, ^{
NSString *plistFilePath = [FIROptions plistFilePathWithName:kServiceInfoFileName];
if (plistFilePath == nil) {
return;
}
sDefaultOptionsDictionary = [NSDictionary dictionaryWithContentsOfFile:plistFilePath];
if (sDefaultOptionsDictionary == nil) {
FIRLogError(kFIRLoggerCore, @"I-COR000011",
@"The configuration file is not a dictionary: "
@"'%@.%@'.",
kServiceInfoFileName, kServiceInfoFileType);
}
});
if (sDefaultOptionsDictionary != nil) {
return sDefaultOptionsDictionary;
}
NSString *plistFilePath = [FIROptions plistFilePathWithName:kServiceInfoFileName];
if (plistFilePath == nil) {
return nil;
}
sDefaultOptionsDictionary = [NSDictionary dictionaryWithContentsOfFile:plistFilePath];
if (sDefaultOptionsDictionary == nil) {
FIRLogError(kFIRLoggerCore, @"I-COR000011",
@"The configuration file is not a dictionary: "
@"'%@.%@'.",
kServiceInfoFileName, kServiceInfoFileType);
}
return sDefaultOptionsDictionary;
}
@ -144,8 +144,6 @@ static dispatch_once_t sDefaultOptionsDictionaryOnceToken;
+ (void)resetDefaultOptions {
sDefaultOptions = nil;
sDefaultOptionsDictionary = nil;
sDefaultOptionsOnceToken = 0;
sDefaultOptionsDictionaryOnceToken = 0;
}
#pragma mark - Private instance methods

View File

@ -1,47 +0,0 @@
/*
* Copyright 2017 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.
*/
#import <Foundation/Foundation.h>
@interface GULAppEnvironmentUtil : NSObject
/// Indicates whether the app is from Apple Store or not. Returns NO if the app is on simulator,
/// development environment or sideloaded.
+ (BOOL)isFromAppStore;
/// Indicates whether the app is a Testflight app. Returns YES if the app has sandbox receipt.
/// Returns NO otherwise.
+ (BOOL)isAppStoreReceiptSandbox;
/// Indicates whether the app is on simulator or not at runtime depending on the device
/// architecture.
+ (BOOL)isSimulator;
/// The current device model. Returns an empty string if device model cannot be retrieved.
+ (NSString *)deviceModel;
/// The current operating system version. Returns an empty string if the system version cannot be
/// retrieved.
+ (NSString *)systemVersion;
/// Indicates whether it is running inside an extension or an app.
+ (BOOL)isAppExtension;
/// @return Returns @YES when is run on iOS version greater or equal to 7.0
+ (BOOL)isIOS7OrHigher DEPRECATED_MSG_ATTRIBUTE(
"Always `YES` because only iOS 8 and higher supported. The method will be removed.");
@end

View File

@ -1,49 +0,0 @@
/*
* 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.
*/
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/// Stores either a date or a dictionary to a specified file.
@interface GULHeartbeatDateStorage : NSObject
- (instancetype)init NS_UNAVAILABLE;
@property(nonatomic, readonly) NSURL *fileURL;
/**
* Default initializer.
* @param fileName The name of the file to store the date information.
* exist, it will be created if needed.
*/
- (instancetype)initWithFileName:(NSString *)fileName;
/**
* Reads the date from the specified file for the given tag.
* @return Returns date if exists, otherwise `nil`.
*/
- (nullable NSDate *)heartbeatDateForTag:(NSString *)tag;
/**
* Saves the date for the specified tag in the specified file.
* @return YES on success, NO otherwise.
*/
- (BOOL)setHearbeatDate:(NSDate *)date forTag:(NSString *)tag;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,79 +0,0 @@
/*
* 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.
*/
#import <Foundation/Foundation.h>
@class FBLPromise<ValueType>;
NS_ASSUME_NONNULL_BEGIN
/// The class provides a convenient abstraction on top of the iOS Keychain API to save data.
@interface GULKeychainStorage : NSObject
- (instancetype)init NS_UNAVAILABLE;
/** Initializes the keychain storage with Keychain Service name.
* @param service A Keychain Service name that will be used to store and retrieve objects. See also
* `kSecAttrService`.
*/
- (instancetype)initWithService:(NSString *)service;
/**
* Get an object by key.
* @param key The key.
* @param objectClass The expected object class required by `NSSecureCoding`.
* @param accessGroup The Keychain Access Group.
*
* @return Returns a promise. It is resolved with an object stored by key if exists. It is resolved
* with `nil` when the object not found. It fails on a Keychain error.
*/
- (FBLPromise<id<NSSecureCoding>> *)getObjectForKey:(NSString *)key
objectClass:(Class)objectClass
accessGroup:(nullable NSString *)accessGroup;
/**
* Saves the given object by the given key.
* @param object The object to store.
* @param key The key to store the object. If there is an existing object by the key, it will be
* overridden.
* @param accessGroup The Keychain Access Group.
*
* @return Returns which is resolved with `[NSNull null]` on success.
*/
- (FBLPromise<NSNull *> *)setObject:(id<NSSecureCoding>)object
forKey:(NSString *)key
accessGroup:(nullable NSString *)accessGroup;
/**
* Removes the object by the given key.
* @param key The key to store the object. If there is an existing object by the key, it will be
* overridden.
* @param accessGroup The Keychain Access Group.
*
* @return Returns which is resolved with `[NSNull null]` on success.
*/
- (FBLPromise<NSNull *> *)removeObjectForKey:(NSString *)key
accessGroup:(nullable NSString *)accessGroup;
#if TARGET_OS_OSX
/// If not `nil`, then only this keychain will be used to save and read data (see
/// `kSecMatchSearchList` and `kSecUseKeychain`. It is mostly intended to be used by unit tests.
@property(nonatomic, nullable) SecKeychainRef keychainRef;
#endif // TARGET_OSX
@end
NS_ASSUME_NONNULL_END

View File

@ -1,61 +0,0 @@
/*
* 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.
*/
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
FOUNDATION_EXPORT NSString *const kGULKeychainUtilsErrorDomain;
/// Helper functions to access Keychain.
@interface GULKeychainUtils : NSObject
/** Fetches a keychain item data matching to the provided query.
* @param query A dictionary with Keychain query parameters. See docs for `SecItemCopyMatching` for
* details.
* @param outError A pointer to `NSError` instance or `NULL`. The instance at `outError` will be
* assigned with an error if there is.
* @returns Data for the first Keychain Item matching the provided query or `nil` if there is not
* such an item (`outError` will be `nil` in this case) or an error occurred.
*/
+ (nullable NSData *)getItemWithQuery:(NSDictionary *)query
error:(NSError *_Nullable *_Nullable)outError;
/** Stores data to a Keychain Item matching to the provided query. An existing Keychain Item
* matching the query parameters will be updated or a new will be created.
* @param item A Keychain Item data to store.
* @param query A dictionary with Keychain query parameters. See docs for `SecItemAdd` and
* `SecItemUpdate` for details.
* @param outError A pointer to `NSError` instance or `NULL`. The instance at `outError` will be
* assigned with an error if there is.
* @returns `YES` when data was successfully stored, `NO` otherwise.
*/
+ (BOOL)setItem:(NSData *)item
withQuery:(NSDictionary *)query
error:(NSError *_Nullable *_Nullable)outError;
/** Removes a Keychain Item matching to the provided query.
* @param query A dictionary with Keychain query parameters. See docs for `SecItemDelete` for
* details.
* @param outError A pointer to `NSError` instance or `NULL`. The instance at `outError` will be
* assigned with an error if there is.
* @returns `YES` if the item was removed successfully or doesn't exist, `NO` otherwise.
*/
+ (BOOL)removeItemWithQuery:(NSDictionary *)query error:(NSError *_Nullable *_Nullable)outError;
@end
NS_ASSUME_NONNULL_END

View File

@ -140,13 +140,21 @@ To ensure that the code is formatted consistently, run the script
before creating a PR.
Travis will verify that any code changes are done in a style compliant way. Install
`clang-format` and `swiftformat`:
`clang-format` and `swiftformat`.
These commands will get the right versions:
```
brew install clang-format
brew install swiftformat
brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/c6f1cbd/Formula/clang-format.rb
brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/c13eda8/Formula/swiftformat.rb
```
Note: if you already have a newer version of these installed you may need to
`brew switch` to this version.
To update this section, find the versions of clang-format and swiftformat.rb to
match the versions in the CI failure logs
[here](https://github.com/Homebrew/homebrew-core/tree/master/Formula).
### Running Unit Tests
Select a scheme and press Command-u to build a component and run its unit tests.
@ -169,7 +177,12 @@ files without real values, but can be replaced with real plist files. To get you
2. Create a new Firebase project, if you don't already have one
3. For each sample app you want to test, create a new Firebase app with the sample app's bundle
identifier (e.g. `com.google.Database-Example`)
4. Download the resulting `GoogleService-Info.plist` and add it to the Xcode project.
4. Download the resulting `GoogleService-Info.plist` and replace the appropriate dummy plist file
(e.g. in [Example/Database/App/](Example/Database/App/));
Some sample apps like Firebase Messaging ([Example/Messaging/App](Example/Messaging/App)) require
special Apple capabilities, and you will have to change the sample app to use a unique bundle
identifier that you can control in your own Apple Developer account.
## Specific Component Instructions
See the sections below for any special instructions for those components.
@ -189,7 +202,7 @@ To run against a local emulator instance, invoke `./scripts/run_database_emulato
running the integration test.
To run against a production instance, provide a valid GoogleServices-Info.plist and copy it to
`FirebaseDatabase/Tests/Resources/GoogleService-Info.plist`. Your Security Rule must be set to
`Example/Database/App/GoogleService-Info.plist`. Your Security Rule must be set to
[public](https://firebase.google.com/docs/database/security/quickstart) while your tests are
running.

View File

@ -17,11 +17,14 @@
#import <objc/runtime.h>
#include <sys/utsname.h>
#import "GoogleDataTransport/GDTCORLibrary/Internal/GoogleDataTransportInternal.h"
#import <GoogleDataTransport/GDTCORConsoleLogger.h>
#import <GoogleDataTransport/GDTCOREvent.h>
#import <GoogleDataTransport/GDTCORTargets.h>
#import <GoogleDataTransport/GDTCORTransport.h>
#import "GoogleUtilities/Environment/Private/GULAppEnvironmentUtil.h"
#import "GoogleUtilities/Environment/Private/GULHeartbeatDateStorage.h"
#import "GoogleUtilities/Logger/Private/GULLogger.h"
#import <GoogleUtilities/GULAppEnvironmentUtil.h>
#import <GoogleUtilities/GULHeartbeatDateStorage.h>
#import <GoogleUtilities/GULLogger.h>
#import "Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsData.h"
#import "Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsInterop.h"

View File

@ -1,47 +0,0 @@
/*
* Copyright 2017 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.
*/
#import <Foundation/Foundation.h>
@interface GULAppEnvironmentUtil : NSObject
/// Indicates whether the app is from Apple Store or not. Returns NO if the app is on simulator,
/// development environment or sideloaded.
+ (BOOL)isFromAppStore;
/// Indicates whether the app is a Testflight app. Returns YES if the app has sandbox receipt.
/// Returns NO otherwise.
+ (BOOL)isAppStoreReceiptSandbox;
/// Indicates whether the app is on simulator or not at runtime depending on the device
/// architecture.
+ (BOOL)isSimulator;
/// The current device model. Returns an empty string if device model cannot be retrieved.
+ (NSString *)deviceModel;
/// The current operating system version. Returns an empty string if the system version cannot be
/// retrieved.
+ (NSString *)systemVersion;
/// Indicates whether it is running inside an extension or an app.
+ (BOOL)isAppExtension;
/// @return Returns @YES when is run on iOS version greater or equal to 7.0
+ (BOOL)isIOS7OrHigher DEPRECATED_MSG_ATTRIBUTE(
"Always `YES` because only iOS 8 and higher supported. The method will be removed.");
@end

View File

@ -1,49 +0,0 @@
/*
* 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.
*/
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/// Stores either a date or a dictionary to a specified file.
@interface GULHeartbeatDateStorage : NSObject
- (instancetype)init NS_UNAVAILABLE;
@property(nonatomic, readonly) NSURL *fileURL;
/**
* Default initializer.
* @param fileName The name of the file to store the date information.
* exist, it will be created if needed.
*/
- (instancetype)initWithFileName:(NSString *)fileName;
/**
* Reads the date from the specified file for the given tag.
* @return Returns date if exists, otherwise `nil`.
*/
- (nullable NSDate *)heartbeatDateForTag:(NSString *)tag;
/**
* Saves the date for the specified tag in the specified file.
* @return YES on success, NO otherwise.
*/
- (BOOL)setHearbeatDate:(NSDate *)date forTag:(NSString *)tag;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,79 +0,0 @@
/*
* 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.
*/
#import <Foundation/Foundation.h>
@class FBLPromise<ValueType>;
NS_ASSUME_NONNULL_BEGIN
/// The class provides a convenient abstraction on top of the iOS Keychain API to save data.
@interface GULKeychainStorage : NSObject
- (instancetype)init NS_UNAVAILABLE;
/** Initializes the keychain storage with Keychain Service name.
* @param service A Keychain Service name that will be used to store and retrieve objects. See also
* `kSecAttrService`.
*/
- (instancetype)initWithService:(NSString *)service;
/**
* Get an object by key.
* @param key The key.
* @param objectClass The expected object class required by `NSSecureCoding`.
* @param accessGroup The Keychain Access Group.
*
* @return Returns a promise. It is resolved with an object stored by key if exists. It is resolved
* with `nil` when the object not found. It fails on a Keychain error.
*/
- (FBLPromise<id<NSSecureCoding>> *)getObjectForKey:(NSString *)key
objectClass:(Class)objectClass
accessGroup:(nullable NSString *)accessGroup;
/**
* Saves the given object by the given key.
* @param object The object to store.
* @param key The key to store the object. If there is an existing object by the key, it will be
* overridden.
* @param accessGroup The Keychain Access Group.
*
* @return Returns which is resolved with `[NSNull null]` on success.
*/
- (FBLPromise<NSNull *> *)setObject:(id<NSSecureCoding>)object
forKey:(NSString *)key
accessGroup:(nullable NSString *)accessGroup;
/**
* Removes the object by the given key.
* @param key The key to store the object. If there is an existing object by the key, it will be
* overridden.
* @param accessGroup The Keychain Access Group.
*
* @return Returns which is resolved with `[NSNull null]` on success.
*/
- (FBLPromise<NSNull *> *)removeObjectForKey:(NSString *)key
accessGroup:(nullable NSString *)accessGroup;
#if TARGET_OS_OSX
/// If not `nil`, then only this keychain will be used to save and read data (see
/// `kSecMatchSearchList` and `kSecUseKeychain`. It is mostly intended to be used by unit tests.
@property(nonatomic, nullable) SecKeychainRef keychainRef;
#endif // TARGET_OSX
@end
NS_ASSUME_NONNULL_END

View File

@ -1,61 +0,0 @@
/*
* 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.
*/
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
FOUNDATION_EXPORT NSString *const kGULKeychainUtilsErrorDomain;
/// Helper functions to access Keychain.
@interface GULKeychainUtils : NSObject
/** Fetches a keychain item data matching to the provided query.
* @param query A dictionary with Keychain query parameters. See docs for `SecItemCopyMatching` for
* details.
* @param outError A pointer to `NSError` instance or `NULL`. The instance at `outError` will be
* assigned with an error if there is.
* @returns Data for the first Keychain Item matching the provided query or `nil` if there is not
* such an item (`outError` will be `nil` in this case) or an error occurred.
*/
+ (nullable NSData *)getItemWithQuery:(NSDictionary *)query
error:(NSError *_Nullable *_Nullable)outError;
/** Stores data to a Keychain Item matching to the provided query. An existing Keychain Item
* matching the query parameters will be updated or a new will be created.
* @param item A Keychain Item data to store.
* @param query A dictionary with Keychain query parameters. See docs for `SecItemAdd` and
* `SecItemUpdate` for details.
* @param outError A pointer to `NSError` instance or `NULL`. The instance at `outError` will be
* assigned with an error if there is.
* @returns `YES` when data was successfully stored, `NO` otherwise.
*/
+ (BOOL)setItem:(NSData *)item
withQuery:(NSDictionary *)query
error:(NSError *_Nullable *_Nullable)outError;
/** Removes a Keychain Item matching to the provided query.
* @param query A dictionary with Keychain query parameters. See docs for `SecItemDelete` for
* details.
* @param outError A pointer to `NSError` instance or `NULL`. The instance at `outError` will be
* assigned with an error if there is.
* @returns `YES` if the item was removed successfully or doesn't exist, `NO` otherwise.
*/
+ (BOOL)removeItemWithQuery:(NSDictionary *)query error:(NSError *_Nullable *_Nullable)outError;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,163 +0,0 @@
/*
* Copyright 2018 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.
*/
#import <Foundation/Foundation.h>
#if SWIFT_PACKAGE
@import GoogleUtilities_Logger;
#else
#import <GoogleUtilities/GULLoggerLevel.h>
#endif
NS_ASSUME_NONNULL_BEGIN
/**
* The services used in the logger.
*/
typedef NSString *const GULLoggerService;
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
/**
* Initialize GULLogger.
*/
extern void GULLoggerInitializeASL(void);
/**
* Override log level to Debug.
*/
void GULLoggerForceDebug(void);
/**
* Turn on logging to STDERR.
*/
extern void GULLoggerEnableSTDERR(void);
/**
* Changes the default logging level of GULLoggerLevelNotice to a user-specified level.
* The default level cannot be set above GULLoggerLevelNotice if the app is running from App Store.
* (required) log level (one of the GULLoggerLevel enum values).
*/
extern void GULSetLoggerLevel(GULLoggerLevel loggerLevel);
/**
* Checks if the specified logger level is loggable given the current settings.
* (required) log level (one of the GULLoggerLevel enum values).
*/
extern BOOL GULIsLoggableLevel(GULLoggerLevel loggerLevel);
/**
* Register version to include in logs.
* (required) version
*/
extern void GULLoggerRegisterVersion(const char *version);
/**
* Logs a message to the Xcode console and the device log. If running from AppStore, will
* not log any messages with a level higher than GULLoggerLevelNotice to avoid log spamming.
* (required) log level (one of the GULLoggerLevel enum values).
* (required) service name of type GULLoggerService.
* (required) message code starting with "I-" which means iOS, followed by a capitalized
* three-character service identifier and a six digit integer message ID that is unique
* within the service.
* An example of the message code is @"I-COR000001".
* (required) message string which can be a format string.
* (optional) variable arguments list obtained from calling va_start, used when message is a format
* string.
*/
extern void GULLogBasic(GULLoggerLevel level,
GULLoggerService service,
BOOL forceLog,
NSString *messageCode,
NSString *message,
// On 64-bit simulators, va_list is not a pointer, so cannot be marked nullable
// See: http://stackoverflow.com/q/29095469
#if __LP64__ && TARGET_OS_SIMULATOR || TARGET_OS_OSX
va_list args_ptr
#else
va_list _Nullable args_ptr
#endif
);
/**
* The following functions accept the following parameters in order:
* (required) service name of type GULLoggerService.
* (required) message code starting from "I-" which means iOS, followed by a capitalized
* three-character service identifier and a six digit integer message ID that is unique
* within the service.
* An example of the message code is @"I-COR000001".
* See go/firebase-log-proposal for details.
* (required) message string which can be a format string.
* (optional) the list of arguments to substitute into the format string.
* Example usage:
* GULLogError(kGULLoggerCore, @"I-COR000001", @"Configuration of %@ failed.", app.name);
*/
extern void GULLogError(GULLoggerService service,
BOOL force,
NSString *messageCode,
NSString *message,
...) NS_FORMAT_FUNCTION(4, 5);
extern void GULLogWarning(GULLoggerService service,
BOOL force,
NSString *messageCode,
NSString *message,
...) NS_FORMAT_FUNCTION(4, 5);
extern void GULLogNotice(GULLoggerService service,
BOOL force,
NSString *messageCode,
NSString *message,
...) NS_FORMAT_FUNCTION(4, 5);
extern void GULLogInfo(GULLoggerService service,
BOOL force,
NSString *messageCode,
NSString *message,
...) NS_FORMAT_FUNCTION(4, 5);
extern void GULLogDebug(GULLoggerService service,
BOOL force,
NSString *messageCode,
NSString *message,
...) NS_FORMAT_FUNCTION(4, 5);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
@interface GULLoggerWrapper : NSObject
/**
* Objective-C wrapper for GULLogBasic to allow weak linking to GULLogger
* (required) log level (one of the GULLoggerLevel enum values).
* (required) service name of type GULLoggerService.
* (required) message code starting with "I-" which means iOS, followed by a capitalized
* three-character service identifier and a six digit integer message ID that is unique
* within the service.
* An example of the message code is @"I-COR000001".
* (required) message string which can be a format string.
* (optional) variable arguments list obtained from calling va_start, used when message is a format
* string.
*/
+ (void)logWithLevel:(GULLoggerLevel)level
withService:(GULLoggerService)service
withCode:(NSString *)messageCode
withMessage:(NSString *)message
withArgs:(va_list)args;
@end
NS_ASSUME_NONNULL_END

View File

@ -140,13 +140,21 @@ To ensure that the code is formatted consistently, run the script
before creating a PR.
Travis will verify that any code changes are done in a style compliant way. Install
`clang-format` and `swiftformat`:
`clang-format` and `swiftformat`.
These commands will get the right versions:
```
brew install clang-format
brew install swiftformat
brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/c6f1cbd/Formula/clang-format.rb
brew upgrade https://raw.githubusercontent.com/Homebrew/homebrew-core/c13eda8/Formula/swiftformat.rb
```
Note: if you already have a newer version of these installed you may need to
`brew switch` to this version.
To update this section, find the versions of clang-format and swiftformat.rb to
match the versions in the CI failure logs
[here](https://github.com/Homebrew/homebrew-core/tree/master/Formula).
### Running Unit Tests
Select a scheme and press Command-u to build a component and run its unit tests.
@ -169,7 +177,12 @@ files without real values, but can be replaced with real plist files. To get you
2. Create a new Firebase project, if you don't already have one
3. For each sample app you want to test, create a new Firebase app with the sample app's bundle
identifier (e.g. `com.google.Database-Example`)
4. Download the resulting `GoogleService-Info.plist` and add it to the Xcode project.
4. Download the resulting `GoogleService-Info.plist` and replace the appropriate dummy plist file
(e.g. in [Example/Database/App/](Example/Database/App/));
Some sample apps like Firebase Messaging ([Example/Messaging/App](Example/Messaging/App)) require
special Apple capabilities, and you will have to change the sample app to use a unique bundle
identifier that you can control in your own Apple Developer account.
## Specific Component Instructions
See the sections below for any special instructions for those components.
@ -189,7 +202,7 @@ To run against a local emulator instance, invoke `./scripts/run_database_emulato
running the integration test.
To run against a production instance, provide a valid GoogleServices-Info.plist and copy it to
`FirebaseDatabase/Tests/Resources/GoogleService-Info.plist`. Your Security Rule must be set to
`Example/Database/App/GoogleService-Info.plist`. Your Security Rule must be set to
[public](https://firebase.google.com/docs/database/security/quickstart) while your tests are
running.

View File

@ -0,0 +1,88 @@
// 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.
#import <Foundation/Foundation.h>
#if CLS_TARGET_OS_HAS_UIKIT
#import <UIKit/UIKit.h>
#endif
__BEGIN_DECLS
#define FIRCLSApplicationActivityDefault \
(NSActivitySuddenTerminationDisabled | NSActivityAutomaticTerminationDisabled)
/**
* Type to indicate application installation source
*/
typedef NS_ENUM(NSInteger, FIRCLSApplicationInstallationSourceType) {
FIRCLSApplicationInstallationSourceTypeDeveloperInstall = 1,
// 2 and 3 are reserved for legacy values.
FIRCLSApplicationInstallationSourceTypeAppStore = 4
};
/**
* Returns the application bundle identifier with occurences of "/" replaced by "_"
*/
NSString* FIRCLSApplicationGetBundleIdentifier(void);
/**
* Returns the SDK's bundle ID
*/
NSString* FIRCLSApplicationGetSDKBundleID(void);
/**
* Returns the platform identifier, either: ios, mac, or tvos.
* Catalyst apps are treated as mac.
*/
NSString* FIRCLSApplicationGetPlatform(void);
/**
* Returns the user-facing app name
*/
NSString* FIRCLSApplicationGetName(void);
/**
* Returns the build number
*/
NSString* FIRCLSApplicationGetBundleVersion(void);
/**
* Returns the human-readable build version
*/
NSString* FIRCLSApplicationGetShortBundleVersion(void);
/**
* Returns a number to indicate how the app has been installed: Developer / App Store
*/
FIRCLSApplicationInstallationSourceType FIRCLSApplicationInstallationSource(void);
BOOL FIRCLSApplicationIsExtension(void);
NSString* FIRCLSApplicationExtensionPointIdentifier(void);
#if CLS_TARGET_OS_HAS_UIKIT
UIApplication* FIRCLSApplicationSharedInstance(void);
#else
id FIRCLSApplicationSharedInstance(void);
#endif
void FIRCLSApplicationOpenURL(NSURL* url,
NSExtensionContext* extensionContext,
void (^completionBlock)(BOOL success));
id<NSObject> FIRCLSApplicationBeginActivity(NSActivityOptions options, NSString* reason);
void FIRCLSApplicationEndActivity(id<NSObject> activity);
void FIRCLSApplicationActivity(NSActivityOptions options, NSString* reason, void (^block)(void));
__END_DECLS

View File

@ -0,0 +1,211 @@
// 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.
#import "FIRCLSApplication.h"
#import "FIRCLSHost.h"
#import "FIRCLSUtility.h"
#if CLS_TARGET_OS_OSX
#import <AppKit/AppKit.h>
#endif
#if CLS_TARGET_OS_HAS_UIKIT
#import <UIKit/UIKit.h>
#endif
NSString* FIRCLSApplicationGetBundleIdentifier(void) {
return [[[NSBundle mainBundle] bundleIdentifier] stringByReplacingOccurrencesOfString:@"/"
withString:@"_"];
}
NSString* FIRCLSApplicationGetSDKBundleID(void) {
return
[@"com.google.firebase.crashlytics." stringByAppendingString:FIRCLSApplicationGetPlatform()];
}
NSString* FIRCLSApplicationGetPlatform(void) {
#if defined(TARGET_OS_MACCATALYST) && TARGET_OS_MACCATALYST
return @"mac";
#elif TARGET_OS_IOS
return @"ios";
#elif TARGET_OS_OSX
return @"mac";
#elif TARGET_OS_TV
return @"tvos";
#endif
}
// these defaults match the FIRCLSInfoPlist helper in FIRCLSIDEFoundation
NSString* FIRCLSApplicationGetBundleVersion(void) {
return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
}
NSString* FIRCLSApplicationGetShortBundleVersion(void) {
return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
}
NSString* FIRCLSApplicationGetName(void) {
NSString* name;
NSBundle* mainBundle;
mainBundle = [NSBundle mainBundle];
name = [mainBundle objectForInfoDictionaryKey:@"CFBundleDisplayName"];
if (name) {
return name;
}
name = [mainBundle objectForInfoDictionaryKey:@"CFBundleName"];
if (name) {
return name;
}
return FIRCLSApplicationGetBundleVersion();
}
BOOL FIRCLSApplicationHasAppStoreReceipt(void) {
NSURL* url = NSBundle.mainBundle.appStoreReceiptURL;
return [NSFileManager.defaultManager fileExistsAtPath:[url path]];
}
FIRCLSApplicationInstallationSourceType FIRCLSApplicationInstallationSource(void) {
if (FIRCLSApplicationHasAppStoreReceipt()) {
return FIRCLSApplicationInstallationSourceTypeAppStore;
}
return FIRCLSApplicationInstallationSourceTypeDeveloperInstall;
}
BOOL FIRCLSApplicationIsExtension(void) {
return FIRCLSApplicationExtensionPointIdentifier() != nil;
}
NSString* FIRCLSApplicationExtensionPointIdentifier(void) {
id extensionDict = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"NSExtension"];
if (!extensionDict) {
return nil;
}
if (![extensionDict isKindOfClass:[NSDictionary class]]) {
FIRCLSSDKLog("Error: NSExtension Info.plist entry is mal-formed\n");
return nil;
}
id typeValue = [(NSDictionary*)extensionDict objectForKey:@"NSExtensionPointIdentifier"];
if (![typeValue isKindOfClass:[NSString class]]) {
FIRCLSSDKLog("Error: NSExtensionPointIdentifier Info.plist entry is mal-formed\n");
return nil;
}
return typeValue;
}
#if CLS_TARGET_OS_HAS_UIKIT
UIApplication* FIRCLSApplicationSharedInstance(void) {
if (FIRCLSApplicationIsExtension()) {
return nil;
}
return [[UIApplication class] performSelector:@selector(sharedApplication)];
}
#elif CLS_TARGET_OS_OSX
id FIRCLSApplicationSharedInstance(void) {
return [NSClassFromString(@"NSApplication") sharedApplication];
}
#else
id FIRCLSApplicationSharedInstance(void) {
return nil; // FIXME: what do we actually return for watch?
}
#endif
void FIRCLSApplicationOpenURL(NSURL* url,
NSExtensionContext* extensionContext,
void (^completionBlock)(BOOL success)) {
if (extensionContext) {
[extensionContext openURL:url completionHandler:completionBlock];
return;
}
BOOL result = NO;
#if TARGET_OS_IOS
// What's going on here is the value returned is a scalar, but we really need an object to
// call this dynamically. Hoops must be jumped.
NSInvocationOperation* op =
[[NSInvocationOperation alloc] initWithTarget:FIRCLSApplicationSharedInstance()
selector:@selector(openURL:)
object:url];
[op start];
[op.result getValue:&result];
#elif CLS_TARGET_OS_OSX
result = [[NSClassFromString(@"NSWorkspace") sharedWorkspace] openURL:url];
#endif
completionBlock(result);
}
id<NSObject> FIRCLSApplicationBeginActivity(NSActivityOptions options, NSString* reason) {
if ([[NSProcessInfo processInfo] respondsToSelector:@selector(beginActivityWithOptions:
reason:)]) {
return [[NSProcessInfo processInfo] beginActivityWithOptions:options reason:reason];
}
#if CLS_TARGET_OS_OSX
if (options & NSActivitySuddenTerminationDisabled) {
[[NSProcessInfo processInfo] disableSuddenTermination];
}
if (options & NSActivityAutomaticTerminationDisabled) {
[[NSProcessInfo processInfo] disableAutomaticTermination:reason];
}
#endif
// encode the options, so we can undo our work later
return @{@"options" : @(options), @"reason" : reason};
}
void FIRCLSApplicationEndActivity(id<NSObject> activity) {
if (!activity) {
return;
}
if ([[NSProcessInfo processInfo] respondsToSelector:@selector(endActivity:)]) {
[[NSProcessInfo processInfo] endActivity:activity];
return;
}
#if CLS_TARGET_OS_OSX
NSInteger options = [[(NSDictionary*)activity objectForKey:@"options"] integerValue];
if (options & NSActivitySuddenTerminationDisabled) {
[[NSProcessInfo processInfo] enableSuddenTermination];
}
if (options & NSActivityAutomaticTerminationDisabled) {
[[NSProcessInfo processInfo]
enableAutomaticTermination:[(NSDictionary*)activity objectForKey:@"reason"]];
}
#endif
}
void FIRCLSApplicationActivity(NSActivityOptions options, NSString* reason, void (^block)(void)) {
id<NSObject> activity = FIRCLSApplicationBeginActivity(options, reason);
block();
FIRCLSApplicationEndActivity(activity);
}

View File

@ -0,0 +1,81 @@
// 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.
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "FIRCLSFeatures.h"
#include "FIRCLSFile.h"
#include "FIRCLSMachO.h"
__BEGIN_DECLS
// Typically, apps seem to have ~300 binary images loaded
#define CLS_BINARY_IMAGE_RUNTIME_NODE_COUNT (512)
#define CLS_BINARY_IMAGE_RUNTIME_NODE_NAME_SIZE (32)
#define CLS_BINARY_IMAGE_RUNTIME_NODE_RECORD_NAME 0
#define FIRCLSUUIDStringLength (33)
typedef struct {
_Atomic(void*) volatile baseAddress;
uint64_t size;
#if CLS_DWARF_UNWINDING_SUPPORTED
const void* ehFrame;
#endif
#if CLS_COMPACT_UNWINDING_SUPPORTED
const void* unwindInfo;
#endif
const void* crashInfo;
#if CLS_BINARY_IMAGE_RUNTIME_NODE_RECORD_NAME
char name[CLS_BINARY_IMAGE_RUNTIME_NODE_NAME_SIZE];
#endif
} FIRCLSBinaryImageRuntimeNode;
typedef struct {
char uuidString[FIRCLSUUIDStringLength];
bool encrypted;
FIRCLSMachOVersion builtSDK;
FIRCLSMachOVersion minSDK;
FIRCLSBinaryImageRuntimeNode node;
struct FIRCLSMachOSlice slice;
intptr_t vmaddr_slide;
} FIRCLSBinaryImageDetails;
typedef struct {
const char* path;
} FIRCLSBinaryImageReadOnlyContext;
typedef struct {
FIRCLSFile file;
FIRCLSBinaryImageRuntimeNode nodes[CLS_BINARY_IMAGE_RUNTIME_NODE_COUNT];
} FIRCLSBinaryImageReadWriteContext;
void FIRCLSBinaryImageInit(FIRCLSBinaryImageReadOnlyContext* roContext,
FIRCLSBinaryImageReadWriteContext* rwContext);
#if CLS_COMPACT_UNWINDING_SUPPORTED
bool FIRCLSBinaryImageSafeFindImageForAddress(uintptr_t address,
FIRCLSBinaryImageRuntimeNode* image);
bool FIRCLSBinaryImageSafeHasUnwindInfo(FIRCLSBinaryImageRuntimeNode* image);
#endif
bool FIRCLSBinaryImageFindImageForUUID(const char* uuidString,
FIRCLSBinaryImageDetails* imageDetails);
bool FIRCLSBinaryImageRecordMainExecutable(FIRCLSFile* file);
__END_DECLS

View File

@ -0,0 +1,571 @@
// 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.
#include "FIRCLSBinaryImage.h"
#include <libkern/OSAtomic.h>
#include <mach-o/dyld.h>
#include <mach-o/getsect.h>
#include <stdatomic.h>
#include "FIRCLSByteUtility.h"
#include "FIRCLSFeatures.h"
#include "FIRCLSFile.h"
#include "FIRCLSGlobals.h"
#include "FIRCLSHost.h"
#include "FIRCLSMachO.h"
#include "FIRCLSUtility.h"
#include <dispatch/dispatch.h>
// this is defined only if __OPEN_SOURCE__ is *not* defined in the TVOS SDK's mach-o/loader.h
// also, it has not yet made it back to the OSX SDKs, for example
#ifndef LC_VERSION_MIN_TVOS
#define LC_VERSION_MIN_TVOS 0x2F
#endif
#pragma mark Prototypes
static bool FIRCLSBinaryImageOpenIfNeeded(bool* needsClosing);
static void FIRCLSBinaryImageAddedCallback(const struct mach_header* mh, intptr_t vmaddr_slide);
static void FIRCLSBinaryImageRemovedCallback(const struct mach_header* mh, intptr_t vmaddr_slide);
static void FIRCLSBinaryImageChanged(bool added,
const struct mach_header* mh,
intptr_t vmaddr_slide);
static bool FIRCLSBinaryImageFillInImageDetails(FIRCLSBinaryImageDetails* details);
static void FIRCLSBinaryImageStoreNode(bool added, FIRCLSBinaryImageDetails imageDetails);
static void FIRCLSBinaryImageRecordSlice(bool added, const FIRCLSBinaryImageDetails imageDetails);
#pragma mark - Core API
void FIRCLSBinaryImageInit(FIRCLSBinaryImageReadOnlyContext* roContext,
FIRCLSBinaryImageReadWriteContext* rwContext) {
// initialize our node array to all zeros
memset(&_firclsContext.writable->binaryImage, 0, sizeof(_firclsContext.writable->binaryImage));
_firclsContext.writable->binaryImage.file.fd = -1;
dispatch_async(FIRCLSGetBinaryImageQueue(), ^{
if (!FIRCLSUnlinkIfExists(_firclsContext.readonly->binaryimage.path)) {
FIRCLSSDKLog("Unable to reset the binary image log file %s\n", strerror(errno));
}
bool needsClosing; // unneeded
if (!FIRCLSBinaryImageOpenIfNeeded(&needsClosing)) {
FIRCLSSDKLog("Error: Unable to open the binary image log file during init\n");
}
});
_dyld_register_func_for_add_image(FIRCLSBinaryImageAddedCallback);
_dyld_register_func_for_remove_image(FIRCLSBinaryImageRemovedCallback);
dispatch_async(FIRCLSGetBinaryImageQueue(), ^{
FIRCLSFileClose(&_firclsContext.writable->binaryImage.file);
});
}
static bool FIRCLSBinaryImageOpenIfNeeded(bool* needsClosing) {
if (!FIRCLSIsValidPointer(_firclsContext.writable)) {
return false;
}
if (!FIRCLSIsValidPointer(_firclsContext.readonly)) {
return false;
}
if (!FIRCLSIsValidPointer(needsClosing)) {
return false;
}
*needsClosing = false;
if (FIRCLSFileIsOpen(&_firclsContext.writable->binaryImage.file)) {
return true;
}
if (!FIRCLSFileInitWithPath(&_firclsContext.writable->binaryImage.file,
_firclsContext.readonly->binaryimage.path, false)) {
FIRCLSSDKLog("Error: unable to open binary image log file\n");
return false;
}
*needsClosing = true;
return true;
}
#if CLS_COMPACT_UNWINDING_SUPPORTED
bool FIRCLSBinaryImageSafeFindImageForAddress(uintptr_t address,
FIRCLSBinaryImageRuntimeNode* image) {
if (!FIRCLSContextIsInitialized()) {
return false;
}
if (address == 0) {
return false;
}
if (!FIRCLSIsValidPointer(image)) {
return false;
}
FIRCLSBinaryImageRuntimeNode* nodes = _firclsContext.writable->binaryImage.nodes;
if (!nodes) {
FIRCLSSDKLogError("The node structure is NULL\n");
return false;
}
for (uint32_t i = 0; i < CLS_BINARY_IMAGE_RUNTIME_NODE_COUNT; ++i) {
FIRCLSBinaryImageRuntimeNode* node = &nodes[i];
if (!FIRCLSIsValidPointer(node)) {
FIRCLSSDKLog(
"Invalid node pointer encountered in context's writable binary image at index %i", i);
continue;
}
if ((address >= (uintptr_t)node->baseAddress) &&
(address < (uintptr_t)node->baseAddress + node->size)) {
*image = *node; // copy the image
return true;
}
}
return false;
}
bool FIRCLSBinaryImageSafeHasUnwindInfo(FIRCLSBinaryImageRuntimeNode* image) {
return FIRCLSIsValidPointer(image->unwindInfo);
}
#endif
bool FIRCLSBinaryImageFindImageForUUID(const char* uuidString,
FIRCLSBinaryImageDetails* imageDetails) {
if (!imageDetails || !uuidString) {
FIRCLSSDKLog("null input\n");
return false;
}
uint32_t imageCount = _dyld_image_count();
for (uint32_t i = 0; i < imageCount; ++i) {
const struct mach_header* mh = _dyld_get_image_header(i);
FIRCLSBinaryImageDetails image;
image.slice = FIRCLSMachOSliceWithHeader((void*)mh);
FIRCLSBinaryImageFillInImageDetails(&image);
if (strncmp(uuidString, image.uuidString, FIRCLSUUIDStringLength) == 0) {
*imageDetails = image;
return true;
}
}
return false;
}
#pragma mark - DYLD callback handlers
static void FIRCLSBinaryImageAddedCallback(const struct mach_header* mh, intptr_t vmaddr_slide) {
FIRCLSBinaryImageChanged(true, mh, vmaddr_slide);
}
static void FIRCLSBinaryImageRemovedCallback(const struct mach_header* mh, intptr_t vmaddr_slide) {
FIRCLSBinaryImageChanged(false, mh, vmaddr_slide);
}
#if CLS_BINARY_IMAGE_RUNTIME_NODE_RECORD_NAME
static bool FIRCLSBinaryImagePopulateRuntimeNodeName(FIRCLSBinaryImageDetails* details) {
if (!FIRCLSIsValidPointer(details)) {
return false;
}
memset(details->node.name, 0, CLS_BINARY_IMAGE_RUNTIME_NODE_NAME_SIZE);
// We have limited storage space for the name. And, we really want to store
// "CoreFoundation", not "/System/Library/Fram", so we have to play tricks
// to make sure we get the right side of the string.
const char* imageName = FIRCLSMachOSliceGetExecutablePath(&details->slice);
if (!imageName) {
return false;
}
const size_t imageNameLength = strlen(imageName);
// Remember to leave one character for null-termination.
if (imageNameLength > CLS_BINARY_IMAGE_RUNTIME_NODE_NAME_SIZE - 1) {
imageName = imageName + (imageNameLength - (CLS_BINARY_IMAGE_RUNTIME_NODE_NAME_SIZE - 1));
}
// subtract one to make sure the string is always null-terminated
strncpy(details->node.name, imageName, CLS_BINARY_IMAGE_RUNTIME_NODE_NAME_SIZE - 1);
return true;
}
#endif
// There were plans later to replace this with FIRCLSMachO
static FIRCLSMachOSegmentCommand FIRCLSBinaryImageMachOGetSegmentCommand(
const struct load_command* cmd) {
FIRCLSMachOSegmentCommand segmentCommand;
memset(&segmentCommand, 0, sizeof(FIRCLSMachOSegmentCommand));
if (!cmd) {
return segmentCommand;
}
if (cmd->cmd == LC_SEGMENT) {
struct segment_command* segCmd = (struct segment_command*)cmd;
memcpy(segmentCommand.segname, segCmd->segname, 16);
segmentCommand.vmaddr = segCmd->vmaddr;
segmentCommand.vmsize = segCmd->vmsize;
} else if (cmd->cmd == LC_SEGMENT_64) {
struct segment_command_64* segCmd = (struct segment_command_64*)cmd;
memcpy(segmentCommand.segname, segCmd->segname, 16);
segmentCommand.vmaddr = segCmd->vmaddr;
segmentCommand.vmsize = segCmd->vmsize;
}
return segmentCommand;
}
static bool FIRCLSBinaryImageMachOSliceInitSectionByName(FIRCLSMachOSliceRef slice,
const char* segName,
const char* sectionName,
FIRCLSMachOSection* section) {
if (!FIRCLSIsValidPointer(slice)) {
return false;
}
if (!section) {
return false;
}
memset(section, 0, sizeof(FIRCLSMachOSection));
if (FIRCLSMachOSliceIs64Bit(slice)) {
const struct section_64* sect =
getsectbynamefromheader_64(slice->startAddress, segName, sectionName);
if (!sect) {
return false;
}
section->addr = sect->addr;
section->size = sect->size;
section->offset = sect->offset;
} else {
const struct section* sect = getsectbynamefromheader(slice->startAddress, segName, sectionName);
if (!sect) {
return false;
}
section->addr = sect->addr;
section->size = sect->size;
section->offset = sect->offset;
}
return true;
}
static bool FIRCLSBinaryImageFillInImageDetails(FIRCLSBinaryImageDetails* details) {
if (!FIRCLSIsValidPointer(details)) {
return false;
}
if (!FIRCLSIsValidPointer(details->slice.startAddress)) {
return false;
}
#if CLS_BINARY_IMAGE_RUNTIME_NODE_RECORD_NAME
// this is done for debugging purposes, so if it fails, its ok to continue
FIRCLSBinaryImagePopulateRuntimeNodeName(details);
#endif
// This cast might look a little dubious, but its just because we're using the same
// struct types in a few different places.
details->node.baseAddress = (void* volatile)details->slice.startAddress;
FIRCLSMachOSliceEnumerateLoadCommands(
&details->slice, ^(uint32_t type, uint32_t size, const struct load_command* cmd) {
switch (type) {
case LC_UUID: {
const uint8_t* uuid = FIRCLSMachOGetUUID(cmd);
FIRCLSSafeHexToString(uuid, 16, details->uuidString);
} break;
case LC_ENCRYPTION_INFO:
details->encrypted = FIRCLSMachOGetEncrypted(cmd);
break;
case LC_SEGMENT:
case LC_SEGMENT_64: {
FIRCLSMachOSegmentCommand segmentCommand = FIRCLSBinaryImageMachOGetSegmentCommand(cmd);
if (strncmp(segmentCommand.segname, SEG_TEXT, sizeof(SEG_TEXT)) == 0) {
details->node.size = segmentCommand.vmsize;
}
} break;
case LC_VERSION_MIN_MACOSX:
case LC_VERSION_MIN_IPHONEOS:
case LC_VERSION_MIN_TVOS:
case LC_VERSION_MIN_WATCHOS:
details->minSDK = FIRCLSMachOGetMinimumOSVersion(cmd);
details->builtSDK = FIRCLSMachOGetLinkedSDKVersion(cmd);
break;
}
});
// We look up the section we want, and we *should* be able to use:
//
// address of data we want = start address + section.offset
//
// However, the offset value is coming back funky in iOS 9. So, instead we look up the address
// the section should be loaded at, and compute the offset by looking up the address of the
// segment itself.
FIRCLSMachOSection section;
#if CLS_COMPACT_UNWINDING_SUPPORTED
if (FIRCLSBinaryImageMachOSliceInitSectionByName(&details->slice, SEG_TEXT, "__unwind_info",
&section)) {
details->node.unwindInfo = (void*)(section.addr + details->vmaddr_slide);
}
#endif
#if CLS_DWARF_UNWINDING_SUPPORTED
if (FIRCLSBinaryImageMachOSliceInitSectionByName(&details->slice, SEG_TEXT, "__eh_frame",
&section)) {
details->node.ehFrame = (void*)(section.addr + details->vmaddr_slide);
}
#endif
if (FIRCLSBinaryImageMachOSliceInitSectionByName(&details->slice, SEG_DATA, "__crash_info",
&section)) {
details->node.crashInfo = (void*)(section.addr + details->vmaddr_slide);
}
return true;
}
static void FIRCLSBinaryImageChanged(bool added,
const struct mach_header* mh,
intptr_t vmaddr_slide) {
// FIRCLSSDKLog("Binary image %s %p\n", added ? "loaded" : "unloaded", mh);
FIRCLSBinaryImageDetails imageDetails;
memset(&imageDetails, 0, sizeof(FIRCLSBinaryImageDetails));
imageDetails.slice = FIRCLSMachOSliceWithHeader((void*)mh);
imageDetails.vmaddr_slide = vmaddr_slide;
FIRCLSBinaryImageFillInImageDetails(&imageDetails);
// this is an atomic operation
FIRCLSBinaryImageStoreNode(added, imageDetails);
// this isn't, so do it on a serial queue
dispatch_async(FIRCLSGetBinaryImageQueue(), ^{
FIRCLSBinaryImageRecordSlice(added, imageDetails);
});
}
#pragma mark - In-Memory Storage
static void FIRCLSBinaryImageStoreNode(bool added, FIRCLSBinaryImageDetails imageDetails) {
// This function is mutating a structure that needs to be accessed at crash time. We
// need to make sure the structure is always in as valid a state as possible.
// FIRCLSSDKLog("Storing %s node %p\n", added ? "loaded" : "unloaded",
// (void*)imageDetails.node.baseAddress);
if (!_firclsContext.writable) {
FIRCLSSDKLog("Error: Writable context is NULL\n");
return;
}
void* searchAddress = NULL;
bool success = false;
FIRCLSBinaryImageRuntimeNode* nodes = _firclsContext.writable->binaryImage.nodes;
if (!added) {
// capture the search address first
searchAddress = imageDetails.node.baseAddress;
// If we are removing a node, we need to set its entries to zero. By clearing all of
// these values, we can just copy in imageDetails.node. Using memset here is slightly
// weird, since we have to restore one field. But, this way, if/when the structure changes,
// we still do the right thing.
memset(&imageDetails.node, 0, sizeof(FIRCLSBinaryImageRuntimeNode));
// restore the baseAddress, which just got zeroed, and is used for indexing
imageDetails.node.baseAddress = searchAddress;
}
for (uint32_t i = 0; i < CLS_BINARY_IMAGE_RUNTIME_NODE_COUNT; ++i) {
FIRCLSBinaryImageRuntimeNode* node = &nodes[i];
if (!node) {
FIRCLSSDKLog("Error: Binary image storage is NULL\n");
break;
}
// navigate through the array, looking for our matching address
if (node->baseAddress != searchAddress) {
continue;
}
// Attempt to swap the base address with whatever we are searching for. Success means that
// entry has been claims/cleared. Failure means some other thread beat us to it.
if (atomic_compare_exchange_strong(&node->baseAddress, &searchAddress,
imageDetails.node.baseAddress)) {
*node = imageDetails.node;
success = true;
break;
}
// If this is an unload, getting here means two threads unloaded at the same time. I think
// that's highly unlikely, and possibly even impossible. So, I'm choosing to abort the process
// at this point.
if (!added) {
FIRCLSSDKLog("Error: Failed to swap during image unload\n");
break;
}
}
if (!success) {
FIRCLSSDKLog("Error: Unable to track a %s node %p\n", added ? "loaded" : "unloaded",
(void*)imageDetails.node.baseAddress);
}
}
#pragma mark - On-Disk Storage
static void FIRCLSBinaryImageRecordDetails(FIRCLSFile* file,
const FIRCLSBinaryImageDetails imageDetails) {
if (!file) {
FIRCLSSDKLog("Error: file is invalid\n");
return;
}
FIRCLSFileWriteHashEntryString(file, "uuid", imageDetails.uuidString);
FIRCLSFileWriteHashEntryUint64(file, "base", (uintptr_t)imageDetails.slice.startAddress);
FIRCLSFileWriteHashEntryUint64(file, "size", imageDetails.node.size);
}
static void FIRCLSBinaryImageRecordLibraryFrameworkInfo(FIRCLSFile* file, const char* path) {
if (!file) {
FIRCLSSDKLog("Error: file is invalid\n");
return;
}
if (!path) {
return;
}
// Because this function is so expensive, we've decided to omit this info for all Apple-supplied
// frameworks. This really isn't that bad, because we can know their info ahead of time (within a
// small margin of error). With this implemenation, we will still record this info for any
// user-built framework, which in the end is the most important thing.
if (strncmp(path, "/System", 7) == 0) {
return;
}
// check to see if this is a potential framework bundle
if (!strstr(path, ".framework")) {
return;
}
// My.framework/Versions/A/My for OS X
// My.framework/My for iOS
NSString* frameworkPath = [NSString stringWithUTF8String:path];
#if TARGET_OS_IPHONE
frameworkPath = [frameworkPath stringByDeletingLastPathComponent];
#else
frameworkPath = [frameworkPath stringByDeletingLastPathComponent]; // My.framework/Versions/A
frameworkPath = [frameworkPath stringByDeletingLastPathComponent]; // My.framework/Versions
frameworkPath = [frameworkPath stringByDeletingLastPathComponent]; // My.framework
#endif
NSBundle* const bundle = [NSBundle bundleWithPath:frameworkPath];
if (!bundle) {
return;
}
FIRCLSFileWriteHashEntryNSStringUnlessNilOrEmpty(file, "bundle_id", [bundle bundleIdentifier]);
FIRCLSFileWriteHashEntryNSStringUnlessNilOrEmpty(
file, "build_version", [bundle objectForInfoDictionaryKey:@"CFBundleVersion"]);
FIRCLSFileWriteHashEntryNSStringUnlessNilOrEmpty(
file, "display_version", [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"]);
}
static void FIRCLSBinaryImageRecordSlice(bool added, const FIRCLSBinaryImageDetails imageDetails) {
bool needsClosing = false;
if (!FIRCLSBinaryImageOpenIfNeeded(&needsClosing)) {
FIRCLSSDKLog("Error: unable to open binary image log file\n");
return;
}
FIRCLSFile* file = &_firclsContext.writable->binaryImage.file;
FIRCLSFileWriteSectionStart(file, added ? "load" : "unload");
FIRCLSFileWriteHashStart(file);
const char* path = FIRCLSMachOSliceGetExecutablePath((FIRCLSMachOSliceRef)&imageDetails.slice);
FIRCLSFileWriteHashEntryString(file, "path", path);
if (added) {
// this won't work if the binary has been unloaded
FIRCLSBinaryImageRecordLibraryFrameworkInfo(file, path);
}
FIRCLSBinaryImageRecordDetails(file, imageDetails);
FIRCLSFileWriteHashEnd(file);
FIRCLSFileWriteSectionEnd(file);
if (needsClosing) {
FIRCLSFileClose(file);
}
}
bool FIRCLSBinaryImageRecordMainExecutable(FIRCLSFile* file) {
FIRCLSBinaryImageDetails imageDetails;
memset(&imageDetails, 0, sizeof(FIRCLSBinaryImageDetails));
imageDetails.slice = FIRCLSMachOSliceGetCurrent();
FIRCLSBinaryImageFillInImageDetails(&imageDetails);
FIRCLSFileWriteSectionStart(file, "executable");
FIRCLSFileWriteHashStart(file);
FIRCLSFileWriteHashEntryString(file, "architecture",
FIRCLSMachOSliceGetArchitectureName(&imageDetails.slice));
FIRCLSBinaryImageRecordDetails(file, imageDetails);
FIRCLSFileWriteHashEntryBoolean(file, "encrypted", imageDetails.encrypted);
FIRCLSFileWriteHashEntryString(file, "minimum_sdk_version",
[FIRCLSMachOFormatVersion(&imageDetails.minSDK) UTF8String]);
FIRCLSFileWriteHashEntryString(file, "built_sdk_version",
[FIRCLSMachOFormatVersion(&imageDetails.builtSDK) UTF8String]);
FIRCLSFileWriteHashEnd(file);
FIRCLSFileWriteSectionEnd(file);
return true;
}

View File

@ -0,0 +1,121 @@
// 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.
#pragma once
#include "FIRCLSAllocate.h"
#include "FIRCLSBinaryImage.h"
#include "FIRCLSException.h"
#include "FIRCLSFeatures.h"
#include "FIRCLSHost.h"
#include "FIRCLSInternalLogging.h"
#include "FIRCLSMachException.h"
#include "FIRCLSSignal.h"
#include "FIRCLSUserLogging.h"
#include <dispatch/dispatch.h>
#include <stdbool.h>
// The purpose of the crash context is to hold values that absolutely must be read and/or written at
// crash time. For robustness against memory corruption, they are protected with guard pages.
// Further, the context is seperated into read-only and read-write sections.
__BEGIN_DECLS
#ifdef __OBJC__
@class FIRCLSInternalReport;
@class FIRCLSSettings;
@class FIRCLSInstallIdentifierModel;
@class FIRCLSFileManager;
#endif
typedef struct {
volatile bool initialized;
volatile bool debuggerAttached;
const char* previouslyCrashedFileFullPath;
const char* logPath;
#if CLS_USE_SIGALTSTACK
void* signalStack;
#endif
#if CLS_MACH_EXCEPTION_SUPPORTED
void* machStack;
#endif
void* delegate;
void* callbackDelegate;
FIRCLSBinaryImageReadOnlyContext binaryimage;
FIRCLSExceptionReadOnlyContext exception;
FIRCLSHostReadOnlyContext host;
FIRCLSSignalReadContext signal;
#if CLS_MACH_EXCEPTION_SUPPORTED
FIRCLSMachExceptionReadContext machException;
#endif
FIRCLSUserLoggingReadOnlyContext logging;
} FIRCLSReadOnlyContext;
typedef struct {
FIRCLSInternalLoggingWritableContext internalLogging;
volatile bool crashOccurred;
FIRCLSBinaryImageReadWriteContext binaryImage;
FIRCLSUserLoggingWritableContext logging;
FIRCLSExceptionWritableContext exception;
} FIRCLSReadWriteContext;
typedef struct {
FIRCLSReadOnlyContext* readonly;
FIRCLSReadWriteContext* writable;
FIRCLSAllocatorRef allocator;
} FIRCLSContext;
typedef struct {
void* delegate;
const char* customBundleId;
const char* rootPath;
const char* previouslyCrashedFileRootPath;
const char* sessionId;
const char* installId;
const char* betaToken;
#if CLS_MACH_EXCEPTION_SUPPORTED
exception_mask_t machExceptionMask;
#endif
bool errorsEnabled;
bool customExceptionsEnabled;
uint32_t maxCustomExceptions;
uint32_t maxErrorLogSize;
uint32_t maxLogSize;
uint32_t maxKeyValues;
} FIRCLSContextInitData;
#ifdef __OBJC__
bool FIRCLSContextInitialize(FIRCLSInternalReport* report,
FIRCLSSettings* settings,
FIRCLSInstallIdentifierModel* installIDModel,
FIRCLSFileManager* fileManager);
// Re-writes the metadata file on the current thread
void FIRCLSContextUpdateMetadata(FIRCLSInternalReport* report,
FIRCLSSettings* settings,
FIRCLSInstallIdentifierModel* installIDModel,
FIRCLSFileManager* fileManager);
#endif
void FIRCLSContextBaseInit(void);
void FIRCLSContextBaseDeinit(void);
bool FIRCLSContextIsInitialized(void);
bool FIRCLSContextHasCrashed(void);
void FIRCLSContextMarkHasCrashed(void);
bool FIRCLSContextMarkAndCheckIfCrashed(void);
__END_DECLS

View File

@ -0,0 +1,468 @@
// 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.
#include "FIRCLSContext.h"
#include <stdlib.h>
#include <string.h>
#import "FIRCLSFileManager.h"
#import "FIRCLSInstallIdentifierModel.h"
#import "FIRCLSInternalReport.h"
#import "FIRCLSSettings.h"
#include "FIRCLSApplication.h"
#include "FIRCLSCrashedMarkerFile.h"
#include "FIRCLSDefines.h"
#include "FIRCLSFeatures.h"
#include "FIRCLSGlobals.h"
#include "FIRCLSProcess.h"
#include "FIRCLSUtility.h"
// The writable size is our handler stack plus whatever scratch we need. We have to use this space
// extremely carefully, however, because thread stacks always needs to be page-aligned. Only the
// first allocation is gauranteed to be page-aligned.
//
// CLS_SIGNAL_HANDLER_STACK_SIZE and CLS_MACH_EXCEPTION_HANDLER_STACK_SIZE are platform dependant,
// defined as 0 for tv/watch.
#define CLS_MINIMUM_READWRITE_SIZE \
(CLS_SIGNAL_HANDLER_STACK_SIZE + CLS_MACH_EXCEPTION_HANDLER_STACK_SIZE + \
sizeof(FIRCLSReadWriteContext))
// We need enough space here for the context, plus storage for strings.
#define CLS_MINIMUM_READABLE_SIZE (sizeof(FIRCLSReadOnlyContext) + 4096 * 4)
static const int64_t FIRCLSContextInitWaitTime = 5LL * NSEC_PER_SEC;
static bool FIRCLSContextRecordMetadata(const char* path, const FIRCLSContextInitData* initData);
static const char* FIRCLSContextAppendToRoot(NSString* root, NSString* component);
static void FIRCLSContextAllocate(FIRCLSContext* context);
FIRCLSContextInitData FIRCLSContextBuildInitData(FIRCLSInternalReport* report,
FIRCLSSettings* settings,
FIRCLSInstallIdentifierModel* installIDModel,
FIRCLSFileManager* fileManager) {
// Because we need to start the crash reporter right away,
// it starts up either with default settings, or cached settings
// from the last time they were fetched
FIRCLSContextInitData initData;
memset(&initData, 0, sizeof(FIRCLSContextInitData));
initData.customBundleId = nil;
initData.installId = [installIDModel.installID UTF8String];
initData.sessionId = [[report identifier] UTF8String];
initData.rootPath = [[report path] UTF8String];
initData.previouslyCrashedFileRootPath = [[fileManager rootPath] UTF8String];
initData.errorsEnabled = [settings errorReportingEnabled];
initData.customExceptionsEnabled = [settings customExceptionsEnabled];
initData.maxCustomExceptions = [settings maxCustomExceptions];
initData.maxErrorLogSize = [settings errorLogBufferSize];
initData.maxLogSize = [settings logBufferSize];
initData.maxKeyValues = [settings maxCustomKeys];
// If this is set, then we could attempt to do a synchronous submission for certain kinds of
// events (exceptions). This is a very cool feature, but adds complexity to the backend. For now,
// we're going to leave this disabled. It does work in the exception case, but will ultimtely
// result in the following crash to be discared. Usually that crash isn't interesting. But, if it
// was, we'd never have a chance to see it.
initData.delegate = nil;
#if CLS_MACH_EXCEPTION_SUPPORTED
__block exception_mask_t mask = 0;
// TODO(b/141241224) This if statement was hardcoded to no, so this block was never run
// FIRCLSSignalEnumerateHandledSignals(^(int idx, int signal) {
// if ([self.delegate ensureDeliveryOfUnixSignal:signal]) {
// mask |= FIRCLSMachExceptionMaskForSignal(signal);
// }
// });
initData.machExceptionMask = mask;
#endif
return initData;
}
bool FIRCLSContextInitialize(FIRCLSInternalReport* report,
FIRCLSSettings* settings,
FIRCLSInstallIdentifierModel* installIDModel,
FIRCLSFileManager* fileManager) {
FIRCLSContextInitData initDataObj =
FIRCLSContextBuildInitData(report, settings, installIDModel, fileManager);
FIRCLSContextInitData* initData = &initDataObj;
if (!initData) {
return false;
}
FIRCLSContextBaseInit();
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
if (!FIRCLSIsValidPointer(initData->rootPath)) {
return false;
}
NSString* rootPath = [NSString stringWithUTF8String:initData->rootPath];
// setup our SDK log file synchronously, because other calls may depend on it
_firclsContext.readonly->logPath = FIRCLSContextAppendToRoot(rootPath, @"sdk.log");
if (!FIRCLSUnlinkIfExists(_firclsContext.readonly->logPath)) {
FIRCLSErrorLog(@"Unable to write initialize SDK write paths %s", strerror(errno));
}
// some values that aren't tied to particular subsystem
_firclsContext.readonly->debuggerAttached = FIRCLSProcessDebuggerAttached();
_firclsContext.readonly->delegate = initData->delegate;
dispatch_group_async(group, queue, ^{
FIRCLSHostInitialize(&_firclsContext.readonly->host);
});
dispatch_group_async(group, queue, ^{
_firclsContext.readonly->logging.errorStorage.maxSize = 0;
_firclsContext.readonly->logging.errorStorage.maxEntries =
initData->errorsEnabled ? initData->maxCustomExceptions : 0;
_firclsContext.readonly->logging.errorStorage.restrictBySize = false;
_firclsContext.readonly->logging.errorStorage.entryCount =
&_firclsContext.writable->logging.errorsCount;
_firclsContext.readonly->logging.errorStorage.aPath =
FIRCLSContextAppendToRoot(rootPath, FIRCLSReportErrorAFile);
_firclsContext.readonly->logging.errorStorage.bPath =
FIRCLSContextAppendToRoot(rootPath, FIRCLSReportErrorBFile);
_firclsContext.readonly->logging.logStorage.maxSize = initData->maxLogSize;
_firclsContext.readonly->logging.logStorage.maxEntries = 0;
_firclsContext.readonly->logging.logStorage.restrictBySize = true;
_firclsContext.readonly->logging.logStorage.entryCount = NULL;
_firclsContext.readonly->logging.logStorage.aPath =
FIRCLSContextAppendToRoot(rootPath, FIRCLSReportLogAFile);
_firclsContext.readonly->logging.logStorage.bPath =
FIRCLSContextAppendToRoot(rootPath, FIRCLSReportLogBFile);
_firclsContext.readonly->logging.customExceptionStorage.aPath =
FIRCLSContextAppendToRoot(rootPath, FIRCLSReportCustomExceptionAFile);
_firclsContext.readonly->logging.customExceptionStorage.bPath =
FIRCLSContextAppendToRoot(rootPath, FIRCLSReportCustomExceptionBFile);
_firclsContext.readonly->logging.customExceptionStorage.maxSize = 0;
_firclsContext.readonly->logging.customExceptionStorage.restrictBySize = false;
_firclsContext.readonly->logging.customExceptionStorage.maxEntries =
initData->maxCustomExceptions;
_firclsContext.readonly->logging.customExceptionStorage.entryCount =
&_firclsContext.writable->exception.customExceptionCount;
_firclsContext.readonly->logging.userKVStorage.maxCount = initData->maxKeyValues;
_firclsContext.readonly->logging.userKVStorage.incrementalPath =
FIRCLSContextAppendToRoot(rootPath, FIRCLSReportUserIncrementalKVFile);
_firclsContext.readonly->logging.userKVStorage.compactedPath =
FIRCLSContextAppendToRoot(rootPath, FIRCLSReportUserCompactedKVFile);
_firclsContext.readonly->logging.internalKVStorage.maxCount = 32; // Hardcode = bad
_firclsContext.readonly->logging.internalKVStorage.incrementalPath =
FIRCLSContextAppendToRoot(rootPath, FIRCLSReportInternalIncrementalKVFile);
_firclsContext.readonly->logging.internalKVStorage.compactedPath =
FIRCLSContextAppendToRoot(rootPath, FIRCLSReportInternalCompactedKVFile);
FIRCLSUserLoggingInit(&_firclsContext.readonly->logging, &_firclsContext.writable->logging);
});
dispatch_group_async(group, queue, ^{
_firclsContext.readonly->binaryimage.path =
FIRCLSContextAppendToRoot(rootPath, FIRCLSReportBinaryImageFile);
FIRCLSBinaryImageInit(&_firclsContext.readonly->binaryimage,
&_firclsContext.writable->binaryImage);
});
dispatch_group_async(group, queue, ^{
NSString* rootPath = [NSString stringWithUTF8String:initData->previouslyCrashedFileRootPath];
NSString* fileName = [NSString stringWithUTF8String:FIRCLSCrashedMarkerFileName];
_firclsContext.readonly->previouslyCrashedFileFullPath =
FIRCLSContextAppendToRoot(rootPath, fileName);
});
if (!_firclsContext.readonly->debuggerAttached) {
dispatch_group_async(group, queue, ^{
_firclsContext.readonly->signal.path =
FIRCLSContextAppendToRoot(rootPath, FIRCLSReportSignalFile);
FIRCLSSignalInitialize(&_firclsContext.readonly->signal);
});
#if CLS_MACH_EXCEPTION_SUPPORTED
dispatch_group_async(group, queue, ^{
_firclsContext.readonly->machException.path =
FIRCLSContextAppendToRoot(rootPath, FIRCLSReportMachExceptionFile);
FIRCLSMachExceptionInit(&_firclsContext.readonly->machException, initData->machExceptionMask);
});
#endif
dispatch_group_async(group, queue, ^{
_firclsContext.readonly->exception.path =
FIRCLSContextAppendToRoot(rootPath, FIRCLSReportExceptionFile);
_firclsContext.readonly->exception.maxCustomExceptions =
initData->customExceptionsEnabled ? initData->maxCustomExceptions : 0;
FIRCLSExceptionInitialize(&_firclsContext.readonly->exception,
&_firclsContext.writable->exception, initData->delegate);
});
} else {
FIRCLSSDKLog("Debugger present - not installing handlers\n");
}
dispatch_group_async(group, queue, ^{
const char* metaDataPath = [[rootPath stringByAppendingPathComponent:FIRCLSReportMetadataFile]
fileSystemRepresentation];
if (!FIRCLSContextRecordMetadata(metaDataPath, initData)) {
FIRCLSSDKLog("Unable to record context metadata\n");
}
});
// At this point we need to do two things. First, we need to do our memory protection *only* after
// all of these initialization steps are really done. But, we also want to wait as long as
// possible for these to be complete. If we do not, there's a chance that we will not be able to
// correctly report a crash shortly after start.
// Note at this will retain the group, so its totally fine to release the group here.
dispatch_group_notify(group, queue, ^{
_firclsContext.readonly->initialized = true;
__sync_synchronize();
if (!FIRCLSAllocatorProtect(_firclsContext.allocator)) {
FIRCLSSDKLog("Error: Memory protection failed\n");
}
});
if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, FIRCLSContextInitWaitTime)) !=
0) {
FIRCLSSDKLog("Error: Delayed initialization\n");
}
return true;
}
void FIRCLSContextUpdateMetadata(FIRCLSInternalReport* report,
FIRCLSSettings* settings,
FIRCLSInstallIdentifierModel* installIDModel,
FIRCLSFileManager* fileManager) {
FIRCLSContextInitData initDataObj =
FIRCLSContextBuildInitData(report, settings, installIDModel, fileManager);
FIRCLSContextInitData* initData = &initDataObj;
NSString* rootPath = [NSString stringWithUTF8String:initData->rootPath];
const char* metaDataPath =
[[rootPath stringByAppendingPathComponent:FIRCLSReportMetadataFile] fileSystemRepresentation];
if (!FIRCLSContextRecordMetadata(metaDataPath, initData)) {
FIRCLSErrorLog(@"Unable to update context metadata");
}
}
void FIRCLSContextBaseInit(void) {
NSString* sdkBundleID = FIRCLSApplicationGetSDKBundleID();
NSString* loggingQueueName = [sdkBundleID stringByAppendingString:@".logging"];
NSString* binaryImagesQueueName = [sdkBundleID stringByAppendingString:@".binary-images"];
NSString* exceptionQueueName = [sdkBundleID stringByAppendingString:@".exception"];
_firclsLoggingQueue = dispatch_queue_create([loggingQueueName UTF8String], DISPATCH_QUEUE_SERIAL);
_firclsBinaryImageQueue =
dispatch_queue_create([binaryImagesQueueName UTF8String], DISPATCH_QUEUE_SERIAL);
_firclsExceptionQueue =
dispatch_queue_create([exceptionQueueName UTF8String], DISPATCH_QUEUE_SERIAL);
FIRCLSContextAllocate(&_firclsContext);
_firclsContext.writable->internalLogging.logFd = -1;
_firclsContext.writable->internalLogging.logLevel = FIRCLSInternalLogLevelDebug;
_firclsContext.writable->crashOccurred = false;
_firclsContext.readonly->initialized = false;
__sync_synchronize();
}
static void FIRCLSContextAllocate(FIRCLSContext* context) {
// create the allocator, and the contexts
// The ordering here is really important, because the "stack" variable must be
// page-aligned. There's no mechanism to ask the allocator to do alignment, but we
// do know the very first allocation in a region is aligned to a page boundary.
context->allocator = FIRCLSAllocatorCreate(CLS_MINIMUM_READWRITE_SIZE, CLS_MINIMUM_READABLE_SIZE);
context->readonly =
FIRCLSAllocatorSafeAllocate(context->allocator, sizeof(FIRCLSReadOnlyContext), CLS_READONLY);
memset(context->readonly, 0, sizeof(FIRCLSReadOnlyContext));
#if CLS_MEMORY_PROTECTION_ENABLED
#if CLS_MACH_EXCEPTION_SUPPORTED
context->readonly->machStack = FIRCLSAllocatorSafeAllocate(
context->allocator, CLS_MACH_EXCEPTION_HANDLER_STACK_SIZE, CLS_READWRITE);
#endif
#if CLS_USE_SIGALTSTACK
context->readonly->signalStack =
FIRCLSAllocatorSafeAllocate(context->allocator, CLS_SIGNAL_HANDLER_STACK_SIZE, CLS_READWRITE);
#endif
#else
#if CLS_MACH_EXCEPTION_SUPPORTED
context->readonly->machStack = valloc(CLS_MACH_EXCEPTION_HANDLER_STACK_SIZE);
#endif
#if CLS_USE_SIGALTSTACK
context->readonly->signalStack = valloc(CLS_SIGNAL_HANDLER_STACK_SIZE);
#endif
#endif
#if CLS_MACH_EXCEPTION_SUPPORTED
memset(_firclsContext.readonly->machStack, 0, CLS_MACH_EXCEPTION_HANDLER_STACK_SIZE);
#endif
#if CLS_USE_SIGALTSTACK
memset(_firclsContext.readonly->signalStack, 0, CLS_SIGNAL_HANDLER_STACK_SIZE);
#endif
context->writable = FIRCLSAllocatorSafeAllocate(context->allocator,
sizeof(FIRCLSReadWriteContext), CLS_READWRITE);
memset(context->writable, 0, sizeof(FIRCLSReadWriteContext));
}
void FIRCLSContextBaseDeinit(void) {
_firclsContext.readonly->initialized = false;
FIRCLSAllocatorDestroy(_firclsContext.allocator);
}
bool FIRCLSContextIsInitialized(void) {
__sync_synchronize();
if (!FIRCLSIsValidPointer(_firclsContext.readonly)) {
return false;
}
return _firclsContext.readonly->initialized;
}
bool FIRCLSContextHasCrashed(void) {
if (!FIRCLSContextIsInitialized()) {
return false;
}
// we've already run a full barrier above, so this read is ok
return _firclsContext.writable->crashOccurred;
}
void FIRCLSContextMarkHasCrashed(void) {
if (!FIRCLSContextIsInitialized()) {
return;
}
_firclsContext.writable->crashOccurred = true;
__sync_synchronize();
}
bool FIRCLSContextMarkAndCheckIfCrashed(void) {
if (!FIRCLSContextIsInitialized()) {
return false;
}
if (_firclsContext.writable->crashOccurred) {
return true;
}
_firclsContext.writable->crashOccurred = true;
__sync_synchronize();
return false;
}
static const char* FIRCLSContextAppendToRoot(NSString* root, NSString* component) {
return FIRCLSDupString(
[[root stringByAppendingPathComponent:component] fileSystemRepresentation]);
}
static bool FIRCLSContextRecordIdentity(FIRCLSFile* file, const FIRCLSContextInitData* initData) {
FIRCLSFileWriteSectionStart(file, "identity");
FIRCLSFileWriteHashStart(file);
FIRCLSFileWriteHashEntryString(file, "generator", CLS_SDK_GENERATOR_NAME);
FIRCLSFileWriteHashEntryString(file, "display_version", CLS_SDK_DISPLAY_VERSION);
FIRCLSFileWriteHashEntryString(file, "build_version", CLS_SDK_DISPLAY_VERSION);
FIRCLSFileWriteHashEntryUint64(file, "started_at", time(NULL));
FIRCLSFileWriteHashEntryString(file, "session_id", initData->sessionId);
FIRCLSFileWriteHashEntryString(file, "install_id", initData->installId);
FIRCLSFileWriteHashEntryString(file, "beta_token", initData->betaToken);
FIRCLSFileWriteHashEntryBoolean(file, "absolute_log_timestamps", true);
FIRCLSFileWriteHashEnd(file);
FIRCLSFileWriteSectionEnd(file);
return true;
}
static bool FIRCLSContextRecordApplication(FIRCLSFile* file, const char* customBundleId) {
FIRCLSFileWriteSectionStart(file, "application");
FIRCLSFileWriteHashStart(file);
FIRCLSFileWriteHashEntryString(file, "bundle_id",
[FIRCLSApplicationGetBundleIdentifier() UTF8String]);
FIRCLSFileWriteHashEntryString(file, "custom_bundle_id", customBundleId);
FIRCLSFileWriteHashEntryString(file, "build_version",
[FIRCLSApplicationGetBundleVersion() UTF8String]);
FIRCLSFileWriteHashEntryString(file, "display_version",
[FIRCLSApplicationGetShortBundleVersion() UTF8String]);
FIRCLSFileWriteHashEntryString(file, "extension_id",
[FIRCLSApplicationExtensionPointIdentifier() UTF8String]);
FIRCLSFileWriteHashEnd(file);
FIRCLSFileWriteSectionEnd(file);
return true;
}
static bool FIRCLSContextRecordMetadata(const char* path, const FIRCLSContextInitData* initData) {
if (!FIRCLSUnlinkIfExists(path)) {
FIRCLSSDKLog("Unable to unlink existing metadata file %s\n", strerror(errno));
}
FIRCLSFile file;
if (!FIRCLSFileInitWithPath(&file, path, false)) {
FIRCLSSDKLog("Unable to open metadata file %s\n", strerror(errno));
return false;
}
if (!FIRCLSContextRecordIdentity(&file, initData)) {
FIRCLSSDKLog("Unable to write out identity metadata\n");
}
if (!FIRCLSHostRecord(&file)) {
FIRCLSSDKLog("Unable to write out host metadata\n");
}
if (!FIRCLSContextRecordApplication(&file, initData->customBundleId)) {
FIRCLSSDKLog("Unable to write out application metadata\n");
}
if (!FIRCLSBinaryImageRecordMainExecutable(&file)) {
FIRCLSSDKLog("Unable to write out executable metadata\n");
}
FIRCLSFileClose(&file);
return true;
}

View File

@ -0,0 +1,31 @@
// 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.
#include "FIRCLSCrashedMarkerFile.h"
#include "FIRCLSFile.h"
#include "FIRCLSUtility.h"
const char *FIRCLSCrashedMarkerFileName = "previously-crashed";
void FIRCLSCreateCrashedMarkerFile() {
FIRCLSFile file;
if (!FIRCLSFileInitWithPath(&file, _firclsContext.readonly->previouslyCrashedFileFullPath, false)) {
FIRCLSSDKLog("Unable to create the crashed marker file\n");
return;
}
FIRCLSFileClose(&file);
FIRCLSSDKLog("Created the crashed marker file\n");
}

View File

@ -0,0 +1,19 @@
// 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.
#include <stdio.h>
extern const char *FIRCLSCrashedMarkerFileName;
void FIRCLSCreateCrashedMarkerFile(void);

View File

@ -0,0 +1,28 @@
// 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.
#include "FIRCLSContext.h"
__BEGIN_DECLS
extern FIRCLSContext _firclsContext;
extern dispatch_queue_t _firclsLoggingQueue;
extern dispatch_queue_t _firclsBinaryImageQueue;
extern dispatch_queue_t _firclsExceptionQueue;
#define FIRCLSGetLoggingQueue() (_firclsLoggingQueue)
#define FIRCLSGetBinaryImageQueue() (_firclsBinaryImageQueue)
#define FIRCLSGetExceptionQueue() (_firclsExceptionQueue)
__END_DECLS

View File

@ -0,0 +1,37 @@
// 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.
#pragma once
#include <mach/vm_types.h>
#include <sys/cdefs.h>
#include "FIRCLSFile.h"
typedef struct {
const char* documentDirectoryPath;
vm_size_t pageSize;
} FIRCLSHostReadOnlyContext;
__BEGIN_DECLS
void FIRCLSHostInitialize(FIRCLSHostReadOnlyContext* roContext);
vm_size_t FIRCLSHostGetPageSize(void);
bool FIRCLSHostRecord(FIRCLSFile* file);
void FIRCLSHostWriteDiskUsage(FIRCLSFile* file);
__END_DECLS

View File

@ -0,0 +1,161 @@
// 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.
#include "FIRCLSHost.h"
#include <mach/mach.h>
#include <sys/mount.h>
#include <sys/sysctl.h>
#import "FIRCLSApplication.h"
#include "FIRCLSDefines.h"
#import "FIRCLSFABHost.h"
#include "FIRCLSFile.h"
#include "FIRCLSGlobals.h"
#include "FIRCLSUtility.h"
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#else
#import <Cocoa/Cocoa.h>
#endif
#define CLS_HOST_SYSCTL_BUFFER_SIZE (128)
#if CLS_CPU_ARM64
#define CLS_MAX_NATIVE_PAGE_SIZE (1024 * 16)
#else
// return 4K, which is correct for all platforms except arm64, currently
#define CLS_MAX_NATIVE_PAGE_SIZE (1024 * 4)
#endif
#define CLS_MIN_NATIVE_PAGE_SIZE (1024 * 4)
#pragma mark Prototypes
static void FIRCLSHostWriteSysctlEntry(
FIRCLSFile* file, const char* key, const char* sysctlKey, void* buffer, size_t bufferSize);
static void FIRCLSHostWriteModelInfo(FIRCLSFile* file);
static void FIRCLSHostWriteOSVersionInfo(FIRCLSFile* file);
#pragma mark - API
void FIRCLSHostInitialize(FIRCLSHostReadOnlyContext* roContext) {
_firclsContext.readonly->host.pageSize = FIRCLSHostGetPageSize();
_firclsContext.readonly->host.documentDirectoryPath = NULL;
// determine where the document directory is mounted, so we can get file system statistics later
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
if ([paths count]) {
_firclsContext.readonly->host.documentDirectoryPath =
FIRCLSDupString([[paths objectAtIndex:0] fileSystemRepresentation]);
}
}
vm_size_t FIRCLSHostGetPageSize(void) {
size_t size;
int pageSize;
// hw.pagesize is defined as HW_PAGESIZE, which is an int. It's important to match
// these types. Turns out that sysctl will not init the data to zero, but it appears
// that sysctlbyname does. This API is nicer, but that's important to keep in mind.
pageSize = 0;
size = sizeof(pageSize);
if (sysctlbyname("hw.pagesize", &pageSize, &size, NULL, 0) != 0) {
FIRCLSSDKLog("sysctlbyname failed while trying to get hw.pagesize\n");
return CLS_MAX_NATIVE_PAGE_SIZE;
}
// if the returned size is not the expected value, abort
if (size != sizeof(pageSize)) {
return CLS_MAX_NATIVE_PAGE_SIZE;
}
// put in some guards to make sure our size is reasonable
if (pageSize > CLS_MAX_NATIVE_PAGE_SIZE) {
return CLS_MAX_NATIVE_PAGE_SIZE;
}
if (pageSize < CLS_MIN_NATIVE_PAGE_SIZE) {
return CLS_MIN_NATIVE_PAGE_SIZE;
}
return pageSize;
}
static void FIRCLSHostWriteSysctlEntry(
FIRCLSFile* file, const char* key, const char* sysctlKey, void* buffer, size_t bufferSize) {
if (sysctlbyname(sysctlKey, buffer, &bufferSize, NULL, 0) != 0) {
FIRCLSFileWriteHashEntryString(file, key, "(failed)");
return;
}
FIRCLSFileWriteHashEntryString(file, key, buffer);
}
static void FIRCLSHostWriteModelInfo(FIRCLSFile* file) {
FIRCLSFileWriteHashEntryString(file, "model", [FIRCLSHostModelInfo() UTF8String]);
// allocate a static buffer for the sysctl values, which are typically
// quite short
char buffer[CLS_HOST_SYSCTL_BUFFER_SIZE];
#if TARGET_OS_EMBEDDED
FIRCLSHostWriteSysctlEntry(file, "machine", "hw.model", buffer, CLS_HOST_SYSCTL_BUFFER_SIZE);
#else
FIRCLSHostWriteSysctlEntry(file, "machine", "hw.machine", buffer, CLS_HOST_SYSCTL_BUFFER_SIZE);
FIRCLSHostWriteSysctlEntry(file, "cpu", "machdep.cpu.brand_string", buffer,
CLS_HOST_SYSCTL_BUFFER_SIZE);
#endif
}
static void FIRCLSHostWriteOSVersionInfo(FIRCLSFile* file) {
FIRCLSFileWriteHashEntryString(file, "os_build_version", [FIRCLSHostOSBuildVersion() UTF8String]);
FIRCLSFileWriteHashEntryString(file, "os_display_version",
[FIRCLSHostOSDisplayVersion() UTF8String]);
FIRCLSFileWriteHashEntryString(file, "platform", [FIRCLSApplicationGetPlatform() UTF8String]);
}
bool FIRCLSHostRecord(FIRCLSFile* file) {
FIRCLSFileWriteSectionStart(file, "host");
FIRCLSFileWriteHashStart(file);
FIRCLSHostWriteModelInfo(file);
FIRCLSHostWriteOSVersionInfo(file);
FIRCLSFileWriteHashEntryString(file, "locale",
[[[NSLocale currentLocale] localeIdentifier] UTF8String]);
FIRCLSFileWriteHashEnd(file);
FIRCLSFileWriteSectionEnd(file);
return true;
}
void FIRCLSHostWriteDiskUsage(FIRCLSFile* file) {
struct statfs tStats;
FIRCLSFileWriteSectionStart(file, "storage");
FIRCLSFileWriteHashStart(file);
if (statfs(_firclsContext.readonly->host.documentDirectoryPath, &tStats) == 0) {
FIRCLSFileWriteHashEntryUint64(file, "free", tStats.f_bavail * tStats.f_bsize);
FIRCLSFileWriteHashEntryUint64(file, "total", tStats.f_blocks * tStats.f_bsize);
}
FIRCLSFileWriteHashEnd(file);
FIRCLSFileWriteSectionEnd(file);
}

View File

@ -0,0 +1,824 @@
// 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.
#include "FIRCLSProcess.h"
#include "FIRCLSDefines.h"
#include "FIRCLSFeatures.h"
#include "FIRCLSGlobals.h"
#include "FIRCLSProfiling.h"
#include "FIRCLSThreadState.h"
#include "FIRCLSUnwind.h"
#include "FIRCLSUtility.h"
#include <dispatch/dispatch.h>
#include <objc/message.h>
#include <pthread.h>
#include <sys/sysctl.h>
#define THREAD_NAME_BUFFER_SIZE (64)
#pragma mark Prototypes
static bool FIRCLSProcessGetThreadName(FIRCLSProcess *process,
thread_t thread,
char *buffer,
size_t length);
static const char *FIRCLSProcessGetThreadDispatchQueueName(FIRCLSProcess *process, thread_t thread);
#pragma mark - API
bool FIRCLSProcessInit(FIRCLSProcess *process, thread_t crashedThread, void *uapVoid) {
if (!process) {
return false;
}
process->task = mach_task_self();
process->thisThread = mach_thread_self();
process->crashedThread = crashedThread;
process->uapVoid = uapVoid;
if (task_threads(process->task, &process->threads, &process->threadCount) != KERN_SUCCESS) {
// failed to get all threads
process->threadCount = 0;
FIRCLSSDKLog("Error: unable to get task threads\n");
return false;
}
return true;
}
bool FIRCLSProcessDestroy(FIRCLSProcess *process) {
return false;
}
// https://developer.apple.com/library/mac/#qa/qa2004/qa1361.html
bool FIRCLSProcessDebuggerAttached(void) {
int junk;
int mib[4];
struct kinfo_proc info;
size_t size;
// Initialize the flags so that, if sysctl fails for some bizarre
// reason, we get a predictable result.
info.kp_proc.p_flag = 0;
// Initialize mib, which tells sysctl the info we want, in this case
// we're looking for information about a specific process ID.
mib[0] = CTL_KERN;
mib[1] = KERN_PROC;
mib[2] = KERN_PROC_PID;
mib[3] = getpid();
// Call sysctl.
size = sizeof(info);
junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0);
if (junk != 0) {
FIRCLSSDKLog("sysctl failed while trying to get kinfo_proc\n");
return false;
}
// We're being debugged if the P_TRACED flag is set.
return (info.kp_proc.p_flag & P_TRACED) != 0;
}
#pragma mark - Thread Support
static bool FIRCLSProcessIsCurrentThread(FIRCLSProcess *process, thread_t thread) {
return MACH_PORT_INDEX(process->thisThread) == MACH_PORT_INDEX(thread);
}
static bool FIRCLSProcessIsCrashedThread(FIRCLSProcess *process, thread_t thread) {
return MACH_PORT_INDEX(process->crashedThread) == MACH_PORT_INDEX(thread);
}
static uint32_t FIRCLSProcessGetThreadCount(FIRCLSProcess *process) {
return process->threadCount;
}
static thread_t FIRCLSProcessGetThread(FIRCLSProcess *process, uint32_t index) {
if (index >= process->threadCount) {
return MACH_PORT_NULL;
}
return process->threads[index];
}
bool FIRCLSProcessSuspendAllOtherThreads(FIRCLSProcess *process) {
mach_msg_type_number_t i;
bool success;
success = true;
for (i = 0; i < process->threadCount; ++i) {
thread_t thread;
thread = FIRCLSProcessGetThread(process, i);
if (FIRCLSProcessIsCurrentThread(process, thread)) {
continue;
}
// FIXME: workaround to get this building on watch, but we need to suspend/resume threads!
#if CLS_CAN_SUSPEND_THREADS
success = success && (thread_suspend(thread) == KERN_SUCCESS);
#endif
}
return success;
}
bool FIRCLSProcessResumeAllOtherThreads(FIRCLSProcess *process) {
mach_msg_type_number_t i;
bool success;
success = true;
for (i = 0; i < process->threadCount; ++i) {
thread_t thread;
thread = FIRCLSProcessGetThread(process, i);
if (FIRCLSProcessIsCurrentThread(process, thread)) {
continue;
}
// FIXME: workaround to get this building on watch, but we need to suspend/resume threads!
#if CLS_CAN_SUSPEND_THREADS
success = success && (thread_resume(thread) == KERN_SUCCESS);
#endif
}
return success;
}
#pragma mark - Thread Properties
void *FIRCLSThreadGetCurrentPC(void) {
return __builtin_return_address(0);
}
static bool FIRCLSProcessGetThreadState(FIRCLSProcess *process,
thread_t thread,
FIRCLSThreadContext *context) {
if (!FIRCLSIsValidPointer(context)) {
FIRCLSSDKLogError("invalid context supplied");
return false;
}
// If the thread context we should use is non-NULL, then just assign it here. Otherwise,
// query the thread state
if (FIRCLSProcessIsCrashedThread(process, thread) && FIRCLSIsValidPointer(process->uapVoid)) {
*context = *((_STRUCT_UCONTEXT *)process->uapVoid)->uc_mcontext;
return true;
}
// Here's a wild trick: emulate what thread_get_state would do. It apppears that
// we cannot reliably unwind out of thread_get_state. So, instead of trying, setup
// a thread context that resembles what the real thing would look like
if (FIRCLSProcessIsCurrentThread(process, thread)) {
FIRCLSSDKLog("Faking current thread\n");
memset(context, 0, sizeof(FIRCLSThreadContext));
// Compute the frame address, and then base the stack value off of that. A frame pushes
// two pointers onto the stack, so we have to offset.
const uintptr_t frameAddress = (uintptr_t)__builtin_frame_address(0);
const uintptr_t stackAddress = FIRCLSUnwindStackPointerFromFramePointer(frameAddress);
#if CLS_CPU_X86_64
context->__ss.__rip = (uintptr_t)FIRCLSThreadGetCurrentPC();
context->__ss.__rbp = frameAddress;
context->__ss.__rsp = stackAddress;
#elif CLS_CPU_I386
context->__ss.__eip = (uintptr_t)FIRCLSThreadGetCurrentPC();
context->__ss.__ebp = frameAddress;
context->__ss.__esp = stackAddress;
#elif CLS_CPU_ARM64
FIRCLSThreadContextSetPC(context, (uintptr_t)FIRCLSThreadGetCurrentPC());
FIRCLSThreadContextSetFramePointer(context, frameAddress);
FIRCLSThreadContextSetLinkRegister(context, (uintptr_t)__builtin_return_address(0));
FIRCLSThreadContextSetStackPointer(context, stackAddress);
#elif CLS_CPU_ARM
context->__ss.__pc = (uintptr_t)FIRCLSThreadGetCurrentPC();
context->__ss.__r[7] = frameAddress;
context->__ss.__lr = (uintptr_t)__builtin_return_address(0);
context->__ss.__sp = stackAddress;
#endif
return true;
}
#if !TARGET_OS_WATCH
// try to get the value by querying the thread state
mach_msg_type_number_t stateCount = FIRCLSThreadStateCount;
if (thread_get_state(thread, FIRCLSThreadState, (thread_state_t)(&(context->__ss)),
&stateCount) != KERN_SUCCESS) {
FIRCLSSDKLogError("failed to get thread state\n");
return false;
}
return true;
#else
return false;
#endif
}
static bool FIRCLSProcessGetThreadName(FIRCLSProcess *process,
thread_t thread,
char *buffer,
size_t length) {
pthread_t pthread;
if (!buffer || length <= 0) {
return false;
}
pthread = pthread_from_mach_thread_np(thread);
return pthread_getname_np(pthread, buffer, length) == 0;
}
static const char *FIRCLSProcessGetThreadDispatchQueueName(FIRCLSProcess *process,
thread_t thread) {
thread_identifier_info_data_t info;
mach_msg_type_number_t infoCount;
dispatch_queue_t *queueAddress;
dispatch_queue_t queue;
const char *string;
infoCount = THREAD_IDENTIFIER_INFO_COUNT;
if (thread_info(thread, THREAD_IDENTIFIER_INFO, (thread_info_t)&info, &infoCount) !=
KERN_SUCCESS) {
FIRCLSSDKLog("unable to get thread info\n");
return NULL;
}
queueAddress = (dispatch_queue_t *)info.dispatch_qaddr;
if (queueAddress == NULL) {
return "";
}
// Sometimes a queue address is invalid. I cannot explain why this is, but
// it can cause a crash.
if (!FIRCLSReadMemory((vm_address_t)queueAddress, &queue, sizeof(void *))) {
return "";
}
// here, we know it is safe to de-reference this address, so attempt to get the queue name
if (!queue) {
return "";
}
string = dispatch_queue_get_label(queue);
// but, we still don't if the entire string is valid, so check that too
if (!FIRCLSReadString((vm_address_t)string, (char **)&string, 128)) {
return "";
}
return string;
}
#pragma mark - Data Recording
static bool FIRCLSProcessRecordThreadRegisters(FIRCLSThreadContext context, FIRCLSFile *file) {
#if CLS_CPU_ARM
FIRCLSFileWriteHashEntryUint64(file, "r0", context.__ss.__r[0]);
FIRCLSFileWriteHashEntryUint64(file, "r1", context.__ss.__r[1]);
FIRCLSFileWriteHashEntryUint64(file, "r2", context.__ss.__r[2]);
FIRCLSFileWriteHashEntryUint64(file, "r3", context.__ss.__r[3]);
FIRCLSFileWriteHashEntryUint64(file, "r4", context.__ss.__r[4]);
FIRCLSFileWriteHashEntryUint64(file, "r5", context.__ss.__r[5]);
FIRCLSFileWriteHashEntryUint64(file, "r6", context.__ss.__r[6]);
FIRCLSFileWriteHashEntryUint64(file, "r7", context.__ss.__r[7]);
FIRCLSFileWriteHashEntryUint64(file, "r8", context.__ss.__r[8]);
FIRCLSFileWriteHashEntryUint64(file, "r9", context.__ss.__r[9]);
FIRCLSFileWriteHashEntryUint64(file, "r10", context.__ss.__r[10]);
FIRCLSFileWriteHashEntryUint64(file, "r11", context.__ss.__r[11]);
FIRCLSFileWriteHashEntryUint64(file, "ip", context.__ss.__r[12]);
FIRCLSFileWriteHashEntryUint64(file, "sp", context.__ss.__sp);
FIRCLSFileWriteHashEntryUint64(file, "lr", context.__ss.__lr);
FIRCLSFileWriteHashEntryUint64(file, "pc", context.__ss.__pc);
FIRCLSFileWriteHashEntryUint64(file, "cpsr", context.__ss.__cpsr);
#elif CLS_CPU_ARM64
FIRCLSFileWriteHashEntryUint64(file, "x0", context.__ss.__x[0]);
FIRCLSFileWriteHashEntryUint64(file, "x1", context.__ss.__x[1]);
FIRCLSFileWriteHashEntryUint64(file, "x2", context.__ss.__x[2]);
FIRCLSFileWriteHashEntryUint64(file, "x3", context.__ss.__x[3]);
FIRCLSFileWriteHashEntryUint64(file, "x4", context.__ss.__x[4]);
FIRCLSFileWriteHashEntryUint64(file, "x5", context.__ss.__x[5]);
FIRCLSFileWriteHashEntryUint64(file, "x6", context.__ss.__x[6]);
FIRCLSFileWriteHashEntryUint64(file, "x7", context.__ss.__x[7]);
FIRCLSFileWriteHashEntryUint64(file, "x8", context.__ss.__x[8]);
FIRCLSFileWriteHashEntryUint64(file, "x9", context.__ss.__x[9]);
FIRCLSFileWriteHashEntryUint64(file, "x10", context.__ss.__x[10]);
FIRCLSFileWriteHashEntryUint64(file, "x11", context.__ss.__x[11]);
FIRCLSFileWriteHashEntryUint64(file, "x12", context.__ss.__x[12]);
FIRCLSFileWriteHashEntryUint64(file, "x13", context.__ss.__x[13]);
FIRCLSFileWriteHashEntryUint64(file, "x14", context.__ss.__x[14]);
FIRCLSFileWriteHashEntryUint64(file, "x15", context.__ss.__x[15]);
FIRCLSFileWriteHashEntryUint64(file, "x16", context.__ss.__x[16]);
FIRCLSFileWriteHashEntryUint64(file, "x17", context.__ss.__x[17]);
FIRCLSFileWriteHashEntryUint64(file, "x18", context.__ss.__x[18]);
FIRCLSFileWriteHashEntryUint64(file, "x19", context.__ss.__x[19]);
FIRCLSFileWriteHashEntryUint64(file, "x20", context.__ss.__x[20]);
FIRCLSFileWriteHashEntryUint64(file, "x21", context.__ss.__x[21]);
FIRCLSFileWriteHashEntryUint64(file, "x22", context.__ss.__x[22]);
FIRCLSFileWriteHashEntryUint64(file, "x23", context.__ss.__x[23]);
FIRCLSFileWriteHashEntryUint64(file, "x24", context.__ss.__x[24]);
FIRCLSFileWriteHashEntryUint64(file, "x25", context.__ss.__x[25]);
FIRCLSFileWriteHashEntryUint64(file, "x26", context.__ss.__x[26]);
FIRCLSFileWriteHashEntryUint64(file, "x27", context.__ss.__x[27]);
FIRCLSFileWriteHashEntryUint64(file, "x28", context.__ss.__x[28]);
FIRCLSFileWriteHashEntryUint64(file, "fp", FIRCLSThreadContextGetFramePointer(&context));
FIRCLSFileWriteHashEntryUint64(file, "sp", FIRCLSThreadContextGetStackPointer(&context));
FIRCLSFileWriteHashEntryUint64(file, "lr", FIRCLSThreadContextGetLinkRegister(&context));
FIRCLSFileWriteHashEntryUint64(file, "pc", FIRCLSThreadContextGetPC(&context));
FIRCLSFileWriteHashEntryUint64(file, "cpsr", context.__ss.__cpsr);
#elif CLS_CPU_I386
FIRCLSFileWriteHashEntryUint64(file, "eax", context.__ss.__eax);
FIRCLSFileWriteHashEntryUint64(file, "ebx", context.__ss.__ebx);
FIRCLSFileWriteHashEntryUint64(file, "ecx", context.__ss.__ecx);
FIRCLSFileWriteHashEntryUint64(file, "edx", context.__ss.__edx);
FIRCLSFileWriteHashEntryUint64(file, "edi", context.__ss.__edi);
FIRCLSFileWriteHashEntryUint64(file, "esi", context.__ss.__esi);
FIRCLSFileWriteHashEntryUint64(file, "ebp", context.__ss.__ebp);
FIRCLSFileWriteHashEntryUint64(file, "esp", context.__ss.__esp);
FIRCLSFileWriteHashEntryUint64(file, "ss", context.__ss.__ss);
FIRCLSFileWriteHashEntryUint64(file, "eflags", context.__ss.__eflags);
FIRCLSFileWriteHashEntryUint64(file, "eip", context.__ss.__eip);
FIRCLSFileWriteHashEntryUint64(file, "cs", context.__ss.__cs);
FIRCLSFileWriteHashEntryUint64(file, "ds", context.__ss.__ds);
FIRCLSFileWriteHashEntryUint64(file, "es", context.__ss.__es);
FIRCLSFileWriteHashEntryUint64(file, "fs", context.__ss.__fs);
FIRCLSFileWriteHashEntryUint64(file, "gs", context.__ss.__gs);
// how do we get the cr2 register?
#elif CLS_CPU_X86_64
FIRCLSFileWriteHashEntryUint64(file, "rax", context.__ss.__rax);
FIRCLSFileWriteHashEntryUint64(file, "rbx", context.__ss.__rbx);
FIRCLSFileWriteHashEntryUint64(file, "rcx", context.__ss.__rcx);
FIRCLSFileWriteHashEntryUint64(file, "rdx", context.__ss.__rdx);
FIRCLSFileWriteHashEntryUint64(file, "rdi", context.__ss.__rdi);
FIRCLSFileWriteHashEntryUint64(file, "rsi", context.__ss.__rsi);
FIRCLSFileWriteHashEntryUint64(file, "rbp", context.__ss.__rbp);
FIRCLSFileWriteHashEntryUint64(file, "rsp", context.__ss.__rsp);
FIRCLSFileWriteHashEntryUint64(file, "r8", context.__ss.__r8);
FIRCLSFileWriteHashEntryUint64(file, "r9", context.__ss.__r9);
FIRCLSFileWriteHashEntryUint64(file, "r10", context.__ss.__r10);
FIRCLSFileWriteHashEntryUint64(file, "r11", context.__ss.__r11);
FIRCLSFileWriteHashEntryUint64(file, "r12", context.__ss.__r12);
FIRCLSFileWriteHashEntryUint64(file, "r13", context.__ss.__r13);
FIRCLSFileWriteHashEntryUint64(file, "r14", context.__ss.__r14);
FIRCLSFileWriteHashEntryUint64(file, "r15", context.__ss.__r15);
FIRCLSFileWriteHashEntryUint64(file, "rip", context.__ss.__rip);
FIRCLSFileWriteHashEntryUint64(file, "rflags", context.__ss.__rflags);
FIRCLSFileWriteHashEntryUint64(file, "cs", context.__ss.__cs);
FIRCLSFileWriteHashEntryUint64(file, "fs", context.__ss.__fs);
FIRCLSFileWriteHashEntryUint64(file, "gs", context.__ss.__gs);
#endif
return true;
}
static bool FIRCLSProcessRecordThread(FIRCLSProcess *process, thread_t thread, FIRCLSFile *file) {
FIRCLSUnwindContext unwindContext;
FIRCLSThreadContext context;
if (!FIRCLSProcessGetThreadState(process, thread, &context)) {
FIRCLSSDKLogError("unable to get thread state");
return false;
}
if (!FIRCLSUnwindInit(&unwindContext, context)) {
FIRCLSSDKLog("unable to init unwind context\n");
return false;
}
FIRCLSFileWriteHashStart(file);
// registers
FIRCLSFileWriteHashKey(file, "registers");
FIRCLSFileWriteHashStart(file);
FIRCLSProcessRecordThreadRegisters(context, file);
FIRCLSFileWriteHashEnd(file);
// stacktrace
FIRCLSFileWriteHashKey(file, "stacktrace");
// stacktrace is an array of integers
FIRCLSFileWriteArrayStart(file);
uint32_t repeatedPCCount = 0;
uint64_t repeatedPC = 0;
const FIRCLSInternalLogLevel level = _firclsContext.writable->internalLogging.logLevel;
while (FIRCLSUnwindNextFrame(&unwindContext)) {
const uintptr_t pc = FIRCLSUnwindGetPC(&unwindContext);
const uint32_t frameCount = FIRCLSUnwindGetFrameRepeatCount(&unwindContext);
if (repeatedPC == pc && repeatedPC != 0) {
// actively counting a recursion
repeatedPCCount = frameCount;
continue;
}
if (frameCount >= FIRCLSUnwindInfiniteRecursionCountThreshold && repeatedPC == 0) {
repeatedPC = pc;
FIRCLSSDKLogWarn("Possible infinite recursion - suppressing logging\n");
_firclsContext.writable->internalLogging.logLevel = FIRCLSInternalLogLevelWarn;
continue;
}
if (repeatedPC != 0) {
// at this point, we've recorded a repeated PC, but it is now no longer
// repeating, so we can restore the logging
_firclsContext.writable->internalLogging.logLevel = level;
}
FIRCLSFileWriteArrayEntryUint64(file, pc);
}
FIRCLSFileWriteArrayEnd(file);
// crashed?
if (FIRCLSProcessIsCrashedThread(process, thread)) {
FIRCLSFileWriteHashEntryBoolean(file, "crashed", true);
}
if (repeatedPC != 0) {
FIRCLSFileWriteHashEntryUint64(file, "repeated_pc", repeatedPC);
FIRCLSFileWriteHashEntryUint64(file, "repeat_count", repeatedPCCount);
}
// Just for extra safety, restore the logging level again. The logic
// above is fairly tricky, this is cheap, and no logging is a real pain.
_firclsContext.writable->internalLogging.logLevel = level;
// end thread info
FIRCLSFileWriteHashEnd(file);
return true;
}
bool FIRCLSProcessRecordAllThreads(FIRCLSProcess *process, FIRCLSFile *file) {
uint32_t threadCount;
uint32_t i;
threadCount = FIRCLSProcessGetThreadCount(process);
FIRCLSFileWriteSectionStart(file, "threads");
FIRCLSFileWriteArrayStart(file);
for (i = 0; i < threadCount; ++i) {
thread_t thread;
thread = FIRCLSProcessGetThread(process, i);
FIRCLSSDKLogInfo("recording thread %d data\n", i);
if (!FIRCLSProcessRecordThread(process, thread, file)) {
return false;
}
}
FIRCLSFileWriteArrayEnd(file);
FIRCLSFileWriteSectionEnd(file);
FIRCLSSDKLogInfo("completed recording all thread data\n");
return true;
}
void FIRCLSProcessRecordThreadNames(FIRCLSProcess *process, FIRCLSFile *file) {
uint32_t threadCount;
uint32_t i;
FIRCLSFileWriteSectionStart(file, "thread_names");
FIRCLSFileWriteArrayStart(file);
threadCount = FIRCLSProcessGetThreadCount(process);
for (i = 0; i < threadCount; ++i) {
thread_t thread;
char name[THREAD_NAME_BUFFER_SIZE];
thread = FIRCLSProcessGetThread(process, i);
name[0] = 0; // null-terminate, just in case nothing is written
FIRCLSProcessGetThreadName(process, thread, name, THREAD_NAME_BUFFER_SIZE);
FIRCLSFileWriteArrayEntryString(file, name);
}
FIRCLSFileWriteArrayEnd(file);
FIRCLSFileWriteSectionEnd(file);
}
void FIRCLSProcessRecordDispatchQueueNames(FIRCLSProcess *process, FIRCLSFile *file) {
uint32_t threadCount;
uint32_t i;
FIRCLSFileWriteSectionStart(file, "dispatch_queue_names");
FIRCLSFileWriteArrayStart(file);
threadCount = FIRCLSProcessGetThreadCount(process);
for (i = 0; i < threadCount; ++i) {
thread_t thread;
const char *name;
thread = FIRCLSProcessGetThread(process, i);
name = FIRCLSProcessGetThreadDispatchQueueName(process, thread);
FIRCLSFileWriteArrayEntryString(file, name);
}
FIRCLSFileWriteArrayEnd(file);
FIRCLSFileWriteSectionEnd(file);
}
#pragma mark - Othe Process Info
bool FIRCLSProcessGetMemoryUsage(uint64_t *active,
uint64_t *inactive,
uint64_t *wired,
uint64_t *freeMem) {
mach_port_t hostPort;
mach_msg_type_number_t hostSize;
vm_size_t pageSize;
vm_statistics_data_t vmStat;
hostPort = mach_host_self();
hostSize = sizeof(vm_statistics_data_t) / sizeof(integer_t);
pageSize = _firclsContext.readonly->host.pageSize;
if (host_statistics(hostPort, HOST_VM_INFO, (host_info_t)&vmStat, &hostSize) != KERN_SUCCESS) {
FIRCLSSDKLog("Failed to get vm statistics\n");
return false;
}
if (!(active && inactive && wired && freeMem)) {
FIRCLSSDKLog("Invalid pointers\n");
return false;
}
// compute the sizes in bytes and return the values
*active = vmStat.active_count * pageSize;
*inactive = vmStat.inactive_count * pageSize;
*wired = vmStat.wire_count * pageSize;
*freeMem = vmStat.free_count * pageSize;
return true;
}
bool FIRCLSProcessGetInfo(FIRCLSProcess *process,
uint64_t *virtualSize,
uint64_t *residentSize,
time_value_t *userTime,
time_value_t *systemTime) {
struct task_basic_info_64 taskInfo;
mach_msg_type_number_t count;
count = TASK_BASIC_INFO_64_COUNT;
if (task_info(process->task, TASK_BASIC_INFO_64, (task_info_t)&taskInfo, &count) !=
KERN_SUCCESS) {
FIRCLSSDKLog("Failed to get task info\n");
return false;
}
if (!(virtualSize && residentSize && userTime && systemTime)) {
FIRCLSSDKLog("Invalid pointers\n");
return false;
}
*virtualSize = taskInfo.virtual_size;
*residentSize = taskInfo.resident_size;
*userTime = taskInfo.user_time;
*systemTime = taskInfo.system_time;
return true;
}
void FIRCLSProcessRecordStats(FIRCLSProcess *process, FIRCLSFile *file) {
uint64_t active;
uint64_t inactive;
uint64_t virtualSize;
uint64_t residentSize;
uint64_t wired;
uint64_t freeMem;
time_value_t userTime;
time_value_t systemTime;
if (!FIRCLSProcessGetMemoryUsage(&active, &inactive, &wired, &freeMem)) {
FIRCLSSDKLog("Unable to get process memory usage\n");
return;
}
if (!FIRCLSProcessGetInfo(process, &virtualSize, &residentSize, &userTime, &systemTime)) {
FIRCLSSDKLog("Unable to get process stats\n");
return;
}
FIRCLSFileWriteSectionStart(file, "process_stats");
FIRCLSFileWriteHashStart(file);
FIRCLSFileWriteHashEntryUint64(file, "active", active);
FIRCLSFileWriteHashEntryUint64(file, "inactive", inactive);
FIRCLSFileWriteHashEntryUint64(file, "wired", wired);
FIRCLSFileWriteHashEntryUint64(file, "freeMem", freeMem); // Intentionally left in, for now. Arg.
FIRCLSFileWriteHashEntryUint64(file, "free_mem", freeMem);
FIRCLSFileWriteHashEntryUint64(file, "virtual", virtualSize);
FIRCLSFileWriteHashEntryUint64(file, "resident", active);
FIRCLSFileWriteHashEntryUint64(file, "user_time",
(userTime.seconds * 1000 * 1000) + userTime.microseconds);
FIRCLSFileWriteHashEntryUint64(file, "sys_time",
(systemTime.seconds * 1000 * 1000) + systemTime.microseconds);
FIRCLSFileWriteHashEnd(file);
FIRCLSFileWriteSectionEnd(file);
}
#pragma mark - Runtime Info
#define OBJC_MSG_SEND_START ((vm_address_t)objc_msgSend)
#define OBJC_MSG_SEND_SUPER_START ((vm_address_t)objc_msgSendSuper)
#define OBJC_MSG_SEND_END (OBJC_MSG_SEND_START + 66)
#define OBJC_MSG_SEND_SUPER_END (OBJC_MSG_SEND_SUPER_START + 66)
#if !CLS_CPU_ARM64
#define OBJC_MSG_SEND_STRET_START ((vm_address_t)objc_msgSend_stret)
#define OBJC_MSG_SEND_SUPER_STRET_START ((vm_address_t)objc_msgSendSuper_stret)
#define OBJC_MSG_SEND_STRET_END (OBJC_MSG_SEND_STRET_START + 66)
#define OBJC_MSG_SEND_SUPER_STRET_END (OBJC_MSG_SEND_SUPER_STRET_START + 66)
#endif
#if CLS_CPU_X86
#define OBJC_MSG_SEND_FPRET_START ((vm_address_t)objc_msgSend_fpret)
#define OBJC_MSG_SEND_FPRET_END (OBJC_MSG_SEND_FPRET_START + 66)
#endif
static const char *FIRCLSProcessGetObjCSelectorName(FIRCLSThreadContext registers) {
void *selectorAddress;
void *selRegister;
#if !CLS_CPU_ARM64
void *stretSelRegister;
#endif
vm_address_t pc;
// First, did we crash in objc_msgSend? The two ways I can think
// of doing this are to use dladdr, and then comparing the strings to
// objc_msg*, or looking up the symbols, and guessing if we are "close enough".
selectorAddress = NULL;
#if CLS_CPU_ARM
pc = registers.__ss.__pc;
selRegister = (void *)registers.__ss.__r[1];
stretSelRegister = (void *)registers.__ss.__r[2];
#elif CLS_CPU_ARM64
pc = FIRCLSThreadContextGetPC(&registers);
selRegister = (void *)registers.__ss.__x[1];
#elif CLS_CPU_I386
pc = registers.__ss.__eip;
selRegister = (void *)registers.__ss.__ecx;
stretSelRegister = (void *)registers.__ss.__ecx;
#elif CLS_CPU_X86_64
pc = registers.__ss.__rip;
selRegister = (void *)registers.__ss.__rsi;
stretSelRegister = (void *)registers.__ss.__rdx;
#endif
if ((pc >= OBJC_MSG_SEND_START) && (pc <= OBJC_MSG_SEND_END)) {
selectorAddress = selRegister;
}
#if !CLS_CPU_ARM64
if ((pc >= OBJC_MSG_SEND_SUPER_START) && (pc <= OBJC_MSG_SEND_SUPER_END)) {
selectorAddress = selRegister;
}
if ((pc >= OBJC_MSG_SEND_STRET_START) && (pc <= OBJC_MSG_SEND_STRET_END)) {
selectorAddress = stretSelRegister;
}
if ((pc >= OBJC_MSG_SEND_SUPER_STRET_START) && (pc <= OBJC_MSG_SEND_SUPER_STRET_END)) {
selectorAddress = stretSelRegister;
}
#if CLS_CPU_X86
if ((pc >= OBJC_MSG_SEND_FPRET_START) && (pc <= OBJC_MSG_SEND_FPRET_END)) {
selectorAddress = selRegister;
}
#endif
#endif
if (!selectorAddress) {
return "";
}
if (!FIRCLSReadString((vm_address_t)selectorAddress, (char **)&selectorAddress, 128)) {
FIRCLSSDKLog("Unable to read the selector string\n");
return "";
}
return selectorAddress;
}
#define CRASH_ALIGN __attribute__((aligned(8)))
typedef struct {
unsigned version CRASH_ALIGN;
const char *message CRASH_ALIGN;
const char *signature CRASH_ALIGN;
const char *backtrace CRASH_ALIGN;
const char *message2 CRASH_ALIGN;
void *reserved CRASH_ALIGN;
void *reserved2 CRASH_ALIGN;
} crash_info_t;
static void FIRCLSProcessRecordCrashInfo(FIRCLSFile *file) {
// TODO: this should be abstracted into binary images, if possible
FIRCLSBinaryImageRuntimeNode *nodes = _firclsContext.writable->binaryImage.nodes;
if (!nodes) {
FIRCLSSDKLogError("The node structure is NULL\n");
return;
}
for (uint32_t i = 0; i < CLS_BINARY_IMAGE_RUNTIME_NODE_COUNT; ++i) {
FIRCLSBinaryImageRuntimeNode *node = &nodes[i];
if (!node->crashInfo) {
continue;
}
crash_info_t info;
if (!FIRCLSReadMemory((vm_address_t)node->crashInfo, &info, sizeof(crash_info_t))) {
continue;
}
FIRCLSSDKLogDebug("Found crash info with version %d\n", info.version);
// Currently support versions 0 through 5.
// 4 was in use for a long time, but it appears that with iOS 9 / swift 2.0, the verison has
// been bumped.
if (info.version > 5) {
continue;
}
if (!info.message) {
continue;
}
#if CLS_BINARY_IMAGE_RUNTIME_NODE_RECORD_NAME
FIRCLSSDKLogInfo("Found crash info for %s\n", node->name);
#endif
FIRCLSSDKLogDebug("attempting to read crash info string\n");
char *string = NULL;
if (!FIRCLSReadString((vm_address_t)info.message, &string, 256)) {
FIRCLSSDKLogError("Failed to copy crash info string\n");
continue;
}
FIRCLSFileWriteArrayEntryHexEncodedString(file, string);
}
}
void FIRCLSProcessRecordRuntimeInfo(FIRCLSProcess *process, FIRCLSFile *file) {
FIRCLSThreadContext mcontext;
if (!FIRCLSProcessGetThreadState(process, process->crashedThread, &mcontext)) {
FIRCLSSDKLogError("unable to get crashed thread state");
}
FIRCLSFileWriteSectionStart(file, "runtime");
FIRCLSFileWriteHashStart(file);
FIRCLSFileWriteHashEntryString(file, "objc_selector", FIRCLSProcessGetObjCSelectorName(mcontext));
FIRCLSFileWriteHashKey(file, "crash_info_entries");
FIRCLSFileWriteArrayStart(file);
FIRCLSProcessRecordCrashInfo(file);
FIRCLSFileWriteArrayEnd(file);
FIRCLSFileWriteHashEnd(file);
FIRCLSFileWriteSectionEnd(file);
}

View File

@ -0,0 +1,45 @@
// 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.
#pragma once
#include <mach/mach.h>
#include <stdbool.h>
#include "FIRCLSFile.h"
typedef struct {
// task info
mach_port_t task;
// thread stuff
thread_t thisThread;
thread_t crashedThread;
thread_act_array_t threads;
mach_msg_type_number_t threadCount;
void *uapVoid; // current thread state
} FIRCLSProcess;
bool FIRCLSProcessInit(FIRCLSProcess *process, thread_t crashedThread, void *uapVoid);
bool FIRCLSProcessDestroy(FIRCLSProcess *process);
bool FIRCLSProcessDebuggerAttached(void);
bool FIRCLSProcessSuspendAllOtherThreads(FIRCLSProcess *process);
bool FIRCLSProcessResumeAllOtherThreads(FIRCLSProcess *process);
void FIRCLSProcessRecordThreadNames(FIRCLSProcess *process, FIRCLSFile *file);
void FIRCLSProcessRecordDispatchQueueNames(FIRCLSProcess *process, FIRCLSFile *file);
bool FIRCLSProcessRecordAllThreads(FIRCLSProcess *process, FIRCLSFile *file);
void FIRCLSProcessRecordStats(FIRCLSProcess *process, FIRCLSFile *file);
void FIRCLSProcessRecordRuntimeInfo(FIRCLSProcess *process, FIRCLSFile *file);

View File

@ -0,0 +1,101 @@
// 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.
#pragma once
#include "FIRCLSFile.h"
__BEGIN_DECLS
#ifdef __OBJC__
extern NSString* const FIRCLSStartTimeKey;
extern NSString* const FIRCLSFirstRunloopTurnTimeKey;
extern NSString* const FIRCLSInBackgroundKey;
#if TARGET_OS_IPHONE
extern NSString* const FIRCLSDeviceOrientationKey;
extern NSString* const FIRCLSUIOrientationKey;
#endif
extern NSString* const FIRCLSUserIdentifierKey;
extern NSString* const FIRCLSUserNameKey;
extern NSString* const FIRCLSUserEmailKey;
extern NSString* const FIRCLSDevelopmentPlatformNameKey;
extern NSString* const FIRCLSDevelopmentPlatformVersionKey;
#endif
extern const uint32_t FIRCLSUserLoggingMaxKVEntries;
typedef struct {
const char* incrementalPath;
const char* compactedPath;
uint32_t maxIncrementalCount;
uint32_t maxCount;
} FIRCLSUserLoggingKVStorage;
typedef struct {
const char* aPath;
const char* bPath;
uint32_t maxSize;
uint32_t maxEntries;
bool restrictBySize;
uint32_t* entryCount;
} FIRCLSUserLoggingABStorage;
typedef struct {
FIRCLSUserLoggingKVStorage userKVStorage;
FIRCLSUserLoggingKVStorage internalKVStorage;
FIRCLSUserLoggingABStorage logStorage;
FIRCLSUserLoggingABStorage errorStorage;
FIRCLSUserLoggingABStorage customExceptionStorage;
} FIRCLSUserLoggingReadOnlyContext;
typedef struct {
const char* activeUserLogPath;
const char* activeErrorLogPath;
const char* activeCustomExceptionPath;
uint32_t userKVCount;
uint32_t internalKVCount;
uint32_t errorsCount;
} FIRCLSUserLoggingWritableContext;
void FIRCLSUserLoggingInit(FIRCLSUserLoggingReadOnlyContext* roContext,
FIRCLSUserLoggingWritableContext* rwContext);
#ifdef __OBJC__
void FIRCLSUserLoggingRecordUserKeyValue(NSString* key, id value);
void FIRCLSUserLoggingRecordInternalKeyValue(NSString* key, id value);
void FIRCLSUserLoggingWriteInternalKeyValue(NSString* key, NSString* value);
void FIRCLSUserLoggingRecordError(NSError* error, NSDictionary<NSString*, id>* additionalUserInfo);
NSDictionary* FIRCLSUserLoggingGetCompactedKVEntries(FIRCLSUserLoggingKVStorage* storage,
bool decodeHex);
void FIRCLSUserLoggingCompactKVEntries(FIRCLSUserLoggingKVStorage* storage);
void FIRCLSUserLoggingRecordKeyValue(NSString* key,
id value,
FIRCLSUserLoggingKVStorage* storage,
uint32_t* counter);
void FIRCLSUserLoggingWriteAndCheckABFiles(FIRCLSUserLoggingABStorage* storage,
const char** activePath,
void (^openedFileBlock)(FIRCLSFile* file));
NSArray* FIRCLSUserLoggingStoredKeyValues(const char* path);
OBJC_EXTERN void FIRCLSLog(NSString* format, ...) NS_FORMAT_FUNCTION(1, 2);
#endif
__END_DECLS

View File

@ -0,0 +1,523 @@
// 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.
#include "FIRCLSUserLogging.h"
#include <sys/time.h>
#include "FIRCLSGlobals.h"
#include "FIRCLSUtility.h"
#import "FIRCLSReportManager_Private.h"
NSString *const FIRCLSStartTimeKey = @"com.crashlytics.kit-start-time";
NSString *const FIRCLSFirstRunloopTurnTimeKey = @"com.crashlytics.first-run-loop-time";
NSString *const FIRCLSInBackgroundKey = @"com.crashlytics.in-background";
#if TARGET_OS_IPHONE
NSString *const FIRCLSDeviceOrientationKey = @"com.crashlytics.device-orientation";
NSString *const FIRCLSUIOrientationKey = @"com.crashlytics.ui-orientation";
#endif
NSString *const FIRCLSUserIdentifierKey = @"com.crashlytics.user-id";
NSString *const FIRCLSDevelopmentPlatformNameKey = @"com.crashlytics.development-platform-name";
NSString *const FIRCLSDevelopmentPlatformVersionKey =
@"com.crashlytics.development-platform-version";
const uint32_t FIRCLSUserLoggingMaxKVEntries = 64;
#pragma mark - Prototypes
static void FIRCLSUserLoggingWriteKeyValue(NSString *key,
NSString *value,
FIRCLSUserLoggingKVStorage *storage,
uint32_t *counter);
static void FIRCLSUserLoggingCheckAndSwapABFiles(FIRCLSUserLoggingABStorage *storage,
const char **activePath,
off_t fileSize);
void FIRCLSLogInternal(NSString *message);
#pragma mark - Setup
void FIRCLSUserLoggingInit(FIRCLSUserLoggingReadOnlyContext *roContext,
FIRCLSUserLoggingWritableContext *rwContext) {
rwContext->activeUserLogPath = roContext->logStorage.aPath;
rwContext->activeErrorLogPath = roContext->errorStorage.aPath;
rwContext->activeCustomExceptionPath = roContext->customExceptionStorage.aPath;
rwContext->userKVCount = 0;
rwContext->internalKVCount = 0;
rwContext->errorsCount = 0;
roContext->userKVStorage.maxIncrementalCount = FIRCLSUserLoggingMaxKVEntries;
roContext->internalKVStorage.maxIncrementalCount = roContext->userKVStorage.maxIncrementalCount;
}
#pragma mark - KV Logging
void FIRCLSUserLoggingRecordInternalKeyValue(NSString *key, id value) {
FIRCLSUserLoggingRecordKeyValue(key, value, &_firclsContext.readonly->logging.internalKVStorage,
&_firclsContext.writable->logging.internalKVCount);
}
void FIRCLSUserLoggingWriteInternalKeyValue(NSString *key, NSString *value) {
// Unsynchronized - must be run on the correct queue
FIRCLSUserLoggingWriteKeyValue(key, value, &_firclsContext.readonly->logging.internalKVStorage,
&_firclsContext.writable->logging.internalKVCount);
}
void FIRCLSUserLoggingRecordUserKeyValue(NSString *key, id value) {
FIRCLSUserLoggingRecordKeyValue(key, value, &_firclsContext.readonly->logging.userKVStorage,
&_firclsContext.writable->logging.userKVCount);
}
static id FIRCLSUserLoggingGetComponent(NSDictionary *entry,
NSString *componentName,
bool decodeHex) {
id value = [entry objectForKey:componentName];
return (decodeHex && value != [NSNull null]) ? FIRCLSFileHexDecodeString([value UTF8String])
: value;
}
static NSString *FIRCLSUserLoggingGetKey(NSDictionary *entry, bool decodeHex) {
return FIRCLSUserLoggingGetComponent(entry, @"key", decodeHex);
}
static id FIRCLSUserLoggingGetValue(NSDictionary *entry, bool decodeHex) {
return FIRCLSUserLoggingGetComponent(entry, @"value", decodeHex);
}
NSDictionary *FIRCLSUserLoggingGetCompactedKVEntries(FIRCLSUserLoggingKVStorage *storage,
bool decodeHex) {
if (!FIRCLSIsValidPointer(storage)) {
FIRCLSSDKLogError("storage invalid\n");
return nil;
}
NSArray *incrementalKVs = FIRCLSUserLoggingStoredKeyValues(storage->incrementalPath);
NSArray *compactedKVs = FIRCLSUserLoggingStoredKeyValues(storage->compactedPath);
NSMutableDictionary *finalKVSet = [NSMutableDictionary new];
// These should all be unique, so there might be a more efficient way to
// do this
for (NSDictionary *entry in compactedKVs) {
NSString *key = FIRCLSUserLoggingGetKey(entry, decodeHex);
NSString *value = FIRCLSUserLoggingGetValue(entry, decodeHex);
if (!key || !value) {
FIRCLSSDKLogError("compacted key/value contains a nil and must be dropped\n");
continue;
}
[finalKVSet setObject:value forKey:key];
}
// Now, assign the incremental values, in file order, so we overwrite any older values.
for (NSDictionary *entry in incrementalKVs) {
NSString *key = FIRCLSUserLoggingGetKey(entry, decodeHex);
NSString *value = FIRCLSUserLoggingGetValue(entry, decodeHex);
if (!key || !value) {
FIRCLSSDKLogError("incremental key/value contains a nil and must be dropped\n");
continue;
}
if ([value isEqual:[NSNull null]]) {
[finalKVSet removeObjectForKey:key];
} else {
[finalKVSet setObject:value forKey:key];
}
}
return finalKVSet;
}
void FIRCLSUserLoggingCompactKVEntries(FIRCLSUserLoggingKVStorage *storage) {
if (!FIRCLSIsValidPointer(storage)) {
FIRCLSSDKLogError("Error: storage invalid\n");
return;
}
NSDictionary *finalKVs = FIRCLSUserLoggingGetCompactedKVEntries(storage, false);
if (unlink(storage->compactedPath) != 0) {
FIRCLSSDKLog("Error: Unable to remove compacted KV store before compaction %s\n",
strerror(errno));
}
FIRCLSFile file;
if (!FIRCLSFileInitWithPath(&file, storage->compactedPath, true)) {
FIRCLSSDKLog("Error: Unable to open compacted k-v file\n");
return;
}
uint32_t maxCount = storage->maxCount;
if ([finalKVs count] > maxCount) {
// We need to remove keys, to avoid going over the max.
// This is just about the worst way to go about doing this. There are lots of smarter ways,
// but it's very uncommon to go down this path.
NSArray *keys = [finalKVs allKeys];
FIRCLSSDKLogInfo("Truncating KV set, which is above max %d\n", maxCount);
finalKVs =
[finalKVs dictionaryWithValuesForKeys:[keys subarrayWithRange:NSMakeRange(0, maxCount)]];
}
for (NSString *key in finalKVs) {
NSString *value = [finalKVs objectForKey:key];
FIRCLSFileWriteSectionStart(&file, "kv");
FIRCLSFileWriteHashStart(&file);
// tricky - the values stored incrementally have already been hex-encoded
FIRCLSFileWriteHashEntryString(&file, "key", [key UTF8String]);
FIRCLSFileWriteHashEntryString(&file, "value", [value UTF8String]);
FIRCLSFileWriteHashEnd(&file);
FIRCLSFileWriteSectionEnd(&file);
}
FIRCLSFileClose(&file);
if (unlink(storage->incrementalPath) != 0) {
FIRCLSSDKLog("Error: Unable to remove incremental KV store after compaction %s\n",
strerror(errno));
}
}
void FIRCLSUserLoggingRecordKeyValue(NSString *key,
id value,
FIRCLSUserLoggingKVStorage *storage,
uint32_t *counter) {
if (!FIRCLSIsValidPointer(key)) {
FIRCLSSDKLogWarn("User provided bad key\n");
return;
}
// ensure that any invalid pointer is actually set to nil
if (!FIRCLSIsValidPointer(value) && value != nil) {
FIRCLSSDKLogWarn("Bad value pointer being clamped to nil\n");
value = nil;
}
if (!FIRCLSContextIsInitialized()) {
return;
}
if ([value respondsToSelector:@selector(description)]) {
value = [value description];
} else {
// passing nil will result in a JSON null being written, which is deserialized as [NSNull null],
// signaling to remove the key during compaction
value = nil;
}
dispatch_sync(FIRCLSGetLoggingQueue(), ^{
FIRCLSUserLoggingWriteKeyValue(key, value, storage, counter);
});
}
static void FIRCLSUserLoggingWriteKeyValue(NSString *key,
NSString *value,
FIRCLSUserLoggingKVStorage *storage,
uint32_t *counter) {
FIRCLSFile file;
if (!FIRCLSIsValidPointer(storage) || !FIRCLSIsValidPointer(counter)) {
FIRCLSSDKLogError("Bad parameters\n");
return;
}
if (!FIRCLSFileInitWithPath(&file, storage->incrementalPath, true)) {
FIRCLSSDKLogError("Unable to open k-v file\n");
return;
}
FIRCLSFileWriteSectionStart(&file, "kv");
FIRCLSFileWriteHashStart(&file);
FIRCLSFileWriteHashEntryHexEncodedString(&file, "key", [key UTF8String]);
FIRCLSFileWriteHashEntryHexEncodedString(&file, "value", [value UTF8String]);
FIRCLSFileWriteHashEnd(&file);
FIRCLSFileWriteSectionEnd(&file);
FIRCLSFileClose(&file);
*counter += 1;
if (*counter >= storage->maxIncrementalCount) {
dispatch_async(FIRCLSGetLoggingQueue(), ^{
FIRCLSUserLoggingCompactKVEntries(storage);
*counter = 0;
});
}
}
NSArray *FIRCLSUserLoggingStoredKeyValues(const char *path) {
if (!FIRCLSContextIsInitialized()) {
return nil;
}
return FIRCLSFileReadSections(path, true, ^NSObject *(id obj) {
return [obj objectForKey:@"kv"];
});
}
#pragma mark - NSError Logging
static void FIRCLSUserLoggingRecordErrorUserInfo(FIRCLSFile *file,
const char *fileKey,
NSDictionary<NSString *, id> *userInfo) {
if ([userInfo count] == 0) {
return;
}
FIRCLSFileWriteHashKey(file, fileKey);
FIRCLSFileWriteArrayStart(file);
for (id key in userInfo) {
id value = [userInfo objectForKey:key];
if (![value respondsToSelector:@selector(description)]) {
continue;
}
FIRCLSFileWriteArrayStart(file);
FIRCLSFileWriteArrayEntryHexEncodedString(file, [key UTF8String]);
FIRCLSFileWriteArrayEntryHexEncodedString(file, [[value description] UTF8String]);
FIRCLSFileWriteArrayEnd(file);
}
FIRCLSFileWriteArrayEnd(file);
}
static void FIRCLSUserLoggingWriteError(FIRCLSFile *file,
NSError *error,
NSDictionary<NSString *, id> *additionalUserInfo,
NSArray *addresses,
uint64_t timestamp) {
FIRCLSFileWriteSectionStart(file, "error");
FIRCLSFileWriteHashStart(file);
FIRCLSFileWriteHashEntryHexEncodedString(file, "domain", [[error domain] UTF8String]);
FIRCLSFileWriteHashEntryInt64(file, "code", [error code]);
FIRCLSFileWriteHashEntryUint64(file, "time", timestamp);
// addresses
FIRCLSFileWriteHashKey(file, "stacktrace");
FIRCLSFileWriteArrayStart(file);
for (NSNumber *address in addresses) {
FIRCLSFileWriteArrayEntryUint64(file, [address unsignedLongLongValue]);
}
FIRCLSFileWriteArrayEnd(file);
// user-info
FIRCLSUserLoggingRecordErrorUserInfo(file, "info", [error userInfo]);
FIRCLSUserLoggingRecordErrorUserInfo(file, "extra_info", additionalUserInfo);
FIRCLSFileWriteHashEnd(file);
FIRCLSFileWriteSectionEnd(file);
}
void FIRCLSUserLoggingRecordError(NSError *error,
NSDictionary<NSString *, id> *additionalUserInfo) {
if (!error) {
return;
}
if (!FIRCLSContextIsInitialized()) {
return;
}
// record the stacktrace and timestamp here, so we
// are as close as possible to the user's log statement
NSArray *addresses = [NSThread callStackReturnAddresses];
uint64_t timestamp = time(NULL);
FIRCLSUserLoggingWriteAndCheckABFiles(
&_firclsContext.readonly->logging.errorStorage,
&_firclsContext.writable->logging.activeErrorLogPath, ^(FIRCLSFile *file) {
FIRCLSUserLoggingWriteError(file, error, additionalUserInfo, addresses, timestamp);
});
}
#pragma mark - CLSLog Support
void FIRCLSLog(NSString *format, ...) {
// If the format is nil do nothing just like NSLog.
if (!format) {
return;
}
va_list args;
va_start(args, format);
NSString *msg = [[NSString alloc] initWithFormat:format arguments:args];
va_end(args);
FIRCLSLogInternal(msg);
}
#pragma mark - Properties
uint32_t FIRCLSUserLoggingMaxLogSize(void) {
// don't forget that the message encoding overhead is 2x, and we
// wrap everything in a json structure with time. So, there is
// quite a penalty
uint32_t size = 1024 * 64;
return size * 2;
}
uint32_t FIRCLSUserLoggingMaxErrorSize(void) {
return FIRCLSUserLoggingMaxLogSize();
}
#pragma mark - AB Logging
void FIRCLSUserLoggingCheckAndSwapABFiles(FIRCLSUserLoggingABStorage *storage,
const char **activePath,
off_t fileSize) {
if (!activePath || !storage) {
return;
}
if (!*activePath) {
return;
}
if (storage->restrictBySize) {
if (fileSize <= storage->maxSize) {
return;
}
} else {
if (!FIRCLSIsValidPointer(storage->entryCount)) {
FIRCLSSDKLogError("Error: storage has invalid pointer, but is restricted by entry count\n");
return;
}
if (*storage->entryCount < storage->maxEntries) {
return;
}
// Here we have rolled over, so we have to reset our counter.
*storage->entryCount = 0;
}
// if it is too big:
// - reset the other log
// - make it active
const char *otherPath = NULL;
if (*activePath == storage->aPath) {
otherPath = storage->bPath;
} else {
// take this path if the pointer is invalid as well, to reset
otherPath = storage->aPath;
}
// guard here against path being nil or empty
NSString *pathString = [NSString stringWithUTF8String:otherPath];
if ([pathString length] > 0) {
// ignore the error, because there is nothing we can do to recover here, and its likely
// any failures would be intermittent
[[NSFileManager defaultManager] removeItemAtPath:pathString error:nil];
}
*activePath = otherPath;
}
void FIRCLSUserLoggingWriteAndCheckABFiles(FIRCLSUserLoggingABStorage *storage,
const char **activePath,
void (^openedFileBlock)(FIRCLSFile *file)) {
if (!storage || !activePath || !openedFileBlock) {
return;
}
if (!*activePath) {
return;
}
if (storage->restrictBySize) {
if (storage->maxSize == 0) {
return;
}
} else {
if (storage->maxEntries == 0) {
return;
}
}
dispatch_sync(FIRCLSGetLoggingQueue(), ^{
FIRCLSFile file;
if (!FIRCLSFileInitWithPath(&file, *activePath, true)) {
FIRCLSSDKLog("Unable to open log file\n");
return;
}
openedFileBlock(&file);
off_t fileSize = 0;
FIRCLSFileCloseWithOffset(&file, &fileSize);
// increment the count before calling FIRCLSUserLoggingCheckAndSwapABFiles, so the value
// reflects the actual amount of stuff written
if (!storage->restrictBySize && FIRCLSIsValidPointer(storage->entryCount)) {
*storage->entryCount += 1;
}
dispatch_async(FIRCLSGetLoggingQueue(), ^{
FIRCLSUserLoggingCheckAndSwapABFiles(storage, activePath, fileSize);
});
});
}
void FIRCLSLogInternalWrite(FIRCLSFile *file, NSString *message, uint64_t time) {
FIRCLSFileWriteSectionStart(file, "log");
FIRCLSFileWriteHashStart(file);
FIRCLSFileWriteHashEntryHexEncodedString(file, "msg", [message UTF8String]);
FIRCLSFileWriteHashEntryUint64(file, "time", time);
FIRCLSFileWriteHashEnd(file);
FIRCLSFileWriteSectionEnd(file);
}
void FIRCLSLogInternal(NSString *message) {
if (!message) {
return;
}
if (!FIRCLSContextIsInitialized()) {
FIRCLSWarningLog(@"WARNING: FIRCLSLog has been used before (or concurrently with) "
@"Crashlytics initialization and cannot be recorded. The message was: \n%@",
message);
return;
}
struct timeval te;
NSUInteger messageLength = [message length];
int maxLogSize = _firclsContext.readonly->logging.logStorage.maxSize;
if (messageLength > maxLogSize) {
FIRCLSWarningLog(
@"WARNING: Attempted to write %zd bytes, but %d is the maximum size of the log. "
@"Truncating to %d bytes.\n",
messageLength, maxLogSize, maxLogSize);
message = [message substringToIndex:maxLogSize];
}
// unable to get time - abort
if (gettimeofday(&te, NULL) != 0) {
return;
}
const uint64_t time = te.tv_sec * 1000LL + te.tv_usec / 1000;
FIRCLSUserLoggingWriteAndCheckABFiles(&_firclsContext.readonly->logging.logStorage,
&_firclsContext.writable->logging.activeUserLogPath,
^(FIRCLSFile *file) {
FIRCLSLogInternalWrite(file, message, time);
});
}

View File

@ -0,0 +1,61 @@
// 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.
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSInteger, FIRCLSNetworkClientErrorType) {
FIRCLSNetworkClientErrorTypeUnknown = -1,
FIRCLSNetworkClientErrorTypeFileUnreadable = -2
};
extern NSString *const FIRCLSNetworkClientErrorDomain;
@protocol FIRCLSNetworkClientDelegate;
@class FIRCLSDataCollectionToken;
@class FIRCLSFileManager;
@interface FIRCLSNetworkClient : NSObject
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
- (instancetype)initWithQueue:(NSOperationQueue *)queue
fileManager:(FIRCLSFileManager *)fileManager
delegate:(id<FIRCLSNetworkClientDelegate>)delegate;
@property(nonatomic, weak) id<FIRCLSNetworkClientDelegate> delegate;
@property(nonatomic, readonly) NSOperationQueue *operationQueue;
@property(nonatomic, readonly) BOOL supportsBackgroundRequests;
- (void)startUploadRequest:(NSURLRequest *)request
filePath:(NSString *)path
dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken
immediately:(BOOL)immediate;
- (void)attemptToReconnectBackgroundSessionWithCompletionBlock:(void (^)(void))completionBlock;
@end
@class FIRCLSNetworkClient;
@protocol FIRCLSNetworkClientDelegate <NSObject>
@required
- (BOOL)networkClientCanUseBackgroundSessions:(FIRCLSNetworkClient *)client;
@optional
- (void)networkClient:(FIRCLSNetworkClient *)client
didFinishUploadWithPath:(NSString *)path
error:(NSError *)error;
@end

View File

@ -0,0 +1,366 @@
// 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.
#import "FIRCLSNetworkClient.h"
#import "FIRCLSApplication.h"
#import "FIRCLSByteUtility.h"
#import "FIRCLSDataCollectionToken.h"
#import "FIRCLSDefines.h"
#import "FIRCLSFileManager.h"
#import "FIRCLSNetworkResponseHandler.h"
#import "FIRCLSURLSession.h"
#import "FIRCLSURLSessionConfiguration.h"
#import "FIRCLSUtility.h"
NSString *const FIRCLSNetworkClientErrorDomain = @"FIRCLSNetworkError";
NSString *const FIRCLSNetworkClientBackgroundIdentifierSuffix = @".crash.background-session";
@interface FIRCLSNetworkClient () <NSURLSessionDelegate> {
NSURLSession *_session;
}
@property(nonatomic, strong) void (^backgroundCompletionHandler)(void);
@property(nonatomic, strong, readonly) NSURLSession *session;
@property(nonatomic, assign) BOOL canUseBackgroundSession;
@property(nonatomic, strong) FIRCLSFileManager *fileManager;
@end
@implementation FIRCLSNetworkClient
- (instancetype)initWithQueue:(NSOperationQueue *)queue
fileManager:(FIRCLSFileManager *)fileManager
delegate:(id<FIRCLSNetworkClientDelegate>)delegate {
self = [super init];
if (!self) {
return nil;
}
_operationQueue = queue;
_delegate = delegate;
_fileManager = fileManager;
self.canUseBackgroundSession = [_delegate networkClientCanUseBackgroundSessions:self];
if (!self.supportsBackgroundRequests) {
FIRCLSDeveloperLog(
"Crashlytics:Crash:Client",
@"Background session uploading not supported, asynchronous uploading will be used");
}
return self;
}
#pragma mark - Background Support
- (NSURLSession *)session {
// Creating a NSURLSession takes some time. Doing it lazily saves us time in the normal case.
if (_session) {
return _session;
}
NSURLSessionConfiguration *config = nil;
Class urlSessionClass;
Class urlSessionConfigurationClass;
#if FIRCLSURLSESSION_REQUIRED
urlSessionClass = [FIRCLSURLSession class];
urlSessionConfigurationClass = [FIRCLSURLSessionConfiguration class];
#else
urlSessionClass = [NSURLSession class];
urlSessionConfigurationClass = [NSURLSessionConfiguration class];
#endif
if (self.supportsBackgroundRequests) {
NSString *sdkBundleID = FIRCLSApplicationGetSDKBundleID();
NSString *backgroundConfigName =
[sdkBundleID stringByAppendingString:FIRCLSNetworkClientBackgroundIdentifierSuffix];
config = [urlSessionConfigurationClass backgroundSessionConfiguration:backgroundConfigName];
#if TARGET_OS_IPHONE
[config setSessionSendsLaunchEvents:NO];
#endif
}
if (!config) {
// take this code path if we don't support background requests OR if we failed to create a
// background configuration
config = [urlSessionConfigurationClass defaultSessionConfiguration];
}
_session = [urlSessionClass sessionWithConfiguration:config
delegate:self
delegateQueue:self.operationQueue];
if (!_session || !config) {
FIRCLSErrorLog(@"Failed to initialize");
}
return _session;
}
#if FIRCLSURLSESSION_REQUIRED
- (BOOL)NSURLSessionAvailable {
if ([[FIRCLSURLSession class] respondsToSelector:@selector(NSURLSessionShouldBeUsed)]) {
return [FIRCLSURLSession NSURLSessionShouldBeUsed];
}
return NSClassFromString(@"NSURLSession") != nil;
}
#endif
- (BOOL)supportsBackgroundRequests {
return !FIRCLSApplicationIsExtension()
#if FIRCLSURLSESSION_REQUIRED
&& [self NSURLSessionAvailable]
#endif
&& self.canUseBackgroundSession;
}
- (void)attemptToReconnectBackgroundSessionWithCompletionBlock:(void (^)(void))completionBlock {
if (!self.supportsBackgroundRequests) {
if (completionBlock) {
completionBlock();
}
return;
}
// This is the absolute minimum necessary. Perhaps we can do better?
if (completionBlock) {
[[NSOperationQueue mainQueue] addOperationWithBlock:completionBlock];
}
}
#pragma mark - API
- (void)startUploadRequest:(NSURLRequest *)request
filePath:(NSString *)path
dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken
immediately:(BOOL)immediate {
if (![dataCollectionToken isValid]) {
FIRCLSErrorLog(@"An upload was requested with an invalid data collection token.");
return;
}
if (immediate) {
[self startImmediateUploadRequest:request filePath:path];
return;
}
NSString *description = [self relativeTaskPathForAbsolutePath:path];
[self checkForExistingTaskMatchingDescription:description
completionBlock:^(BOOL found) {
if (found) {
FIRCLSDeveloperLog(
"Crashlytics:Crash:Client",
@"A task currently exists for this upload, skipping");
return;
}
[self startNewUploadRequest:request filePath:path];
}];
}
#pragma mark - Support API
- (void)startImmediateUploadRequest:(NSURLRequest *)request filePath:(NSString *)path {
// check the ivar directly, to avoid going back to the delegate
if (self.supportsBackgroundRequests) {
// this can be done here, because the request will be started synchronously.
[self startNewUploadRequest:request filePath:path];
return;
}
if (![[NSFileManager defaultManager] isReadableFileAtPath:path]) {
FIRCLSSDKLog("Error: file unreadable\n");
// Following the same logic as below, do not try to inform the delegate
return;
}
NSMutableURLRequest *mutableRequest = [request mutableCopy];
[mutableRequest setHTTPBodyStream:[NSInputStream inputStreamWithFileAtPath:path]];
NSURLResponse *requestResponse = nil;
[[NSURLSession sharedSession]
dataTaskWithRequest:mutableRequest
completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response,
NSError *_Nullable error) {
[FIRCLSNetworkResponseHandler
clientResponseType:requestResponse
handler:^(FIRCLSNetworkClientResponseType type, NSInteger statusCode) {
if (type != FIRCLSNetworkClientResponseSuccess) {
// don't even inform the delegate of a failure here, because we don't
// want any action to be taken synchronously
return;
}
[[self delegate] networkClient:self
didFinishUploadWithPath:path
error:error];
}];
}];
}
- (void)startNewUploadRequest:(NSURLRequest *)request filePath:(NSString *)path {
if (![[NSFileManager defaultManager] isReadableFileAtPath:path]) {
[self.operationQueue addOperationWithBlock:^{
[self
handleTaskDescription:path
completedWithError:[NSError errorWithDomain:FIRCLSNetworkClientErrorDomain
code:FIRCLSNetworkClientErrorTypeFileUnreadable
userInfo:@{@"path" : path}]];
}];
return;
}
NSURLSessionUploadTask *task = [self.session uploadTaskWithRequest:request
fromFile:[NSURL fileURLWithPath:path]];
// set the description, so we can determine what file was successfully uploaded later on
[task setTaskDescription:[self relativeTaskPathForAbsolutePath:path]];
[task resume];
}
- (NSString *)rootPath {
return self.fileManager.rootPath;
}
- (NSString *)absolutePathForRelativeTaskPath:(NSString *)path {
return [self.rootPath stringByAppendingPathComponent:path];
}
- (NSString *)relativeTaskPathForAbsolutePath:(NSString *)path {
// make sure this has a tailing slash, so the path looks relative
NSString *root = [self.rootPath stringByAppendingString:@"/"];
if (![path hasPrefix:root]) {
FIRCLSSDKLog("Error: path '%s' is not at the root '%s'", [path UTF8String], [root UTF8String]);
return nil;
}
return [path stringByReplacingOccurrencesOfString:root withString:@""];
}
#pragma mark - Task Management
- (BOOL)taskArray:(NSArray *)array hasTaskMatchingDescription:(NSString *)description {
NSUInteger idx = [array indexOfObjectPassingTest:^BOOL(id obj, NSUInteger arrayIdx, BOOL *stop) {
return [[obj taskDescription] isEqualToString:description];
}];
return idx != NSNotFound;
}
- (void)checkSession:(NSURLSession *)session
forTasksMatchingDescription:(NSString *)description
completionBlock:(void (^)(BOOL found))block {
if (!session) {
block(NO);
return;
}
[session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks,
NSArray *downloadTasks) {
if ([self taskArray:uploadTasks hasTaskMatchingDescription:description]) {
block(YES);
return;
}
if ([self taskArray:dataTasks hasTaskMatchingDescription:description]) {
block(YES);
return;
}
if ([self taskArray:downloadTasks hasTaskMatchingDescription:description]) {
block(YES);
return;
}
block(NO);
}];
}
- (void)checkForExistingTaskMatchingDescription:(NSString *)description
completionBlock:(void (^)(BOOL found))block {
// Do not instantiate the normal session, because if it doesn't exist yet, it cannot possibly have
// existing tasks
[_operationQueue addOperationWithBlock:^{
[self checkSession:self.session
forTasksMatchingDescription:description
completionBlock:^(BOOL found) {
block(found);
}];
}];
}
#pragma mark - Result Handling
// This method is duplicated from FIRCLSFABNetworkClient. Sharing it is a little weird - I didn't
// feel like it fit into FIRCLSNetworkResponseHandler.
- (void)runAfterRetryValueFromResponse:(NSURLResponse *)response block:(void (^)(void))block {
NSTimeInterval delay = [FIRCLSNetworkResponseHandler retryValueForResponse:response];
// FIRCLSDeveloperLog("Network", @"Restarting request after %f", delay);
FIRCLSAddOperationAfter(delay, _operationQueue, block);
}
- (void)restartTask:(NSURLSessionTask *)task {
NSURLRequest *request = [task originalRequest];
[self runAfterRetryValueFromResponse:[task response]
block:^{
NSString *path = [self
absolutePathForRelativeTaskPath:[task taskDescription]];
[self startNewUploadRequest:request filePath:path];
}];
}
- (void)handleTask:(NSURLSessionTask *)task completedWithError:(NSError *)error {
[self handleTaskDescription:[task taskDescription] completedWithError:error];
}
- (void)handleTaskDescription:(NSString *)taskDescription completedWithError:(NSError *)error {
NSString *path = [self absolutePathForRelativeTaskPath:taskDescription];
[[self delegate] networkClient:self didFinishUploadWithPath:path error:error];
}
#pragma mark - NSURLSessionDelegate
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error {
FIRCLSDeveloperLog("Crashlytics:Crash:Client", @"session became invalid: %@", error);
}
// Careful! Not implementing this method appears to cause a crash when using a background task
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error {
[FIRCLSNetworkResponseHandler handleCompletedResponse:task.response
forOriginalRequest:task.originalRequest
error:error
block:^(BOOL restart, NSError *taskError) {
if (restart) {
[self restartTask:task];
return;
}
[self handleTask:task
completedWithError:taskError];
}];
}
@end

View File

@ -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.
#import <Foundation/Foundation.h>
#include "FIRCLSApplicationIdentifierModel.h"
#include "FIRCLSProfiling.h"
#include "FIRCrashlytics.h"
@class FBLPromise<T>;
NS_ASSUME_NONNULL_BEGIN
@class FIRCLSDataCollectionArbiter;
@class FIRCLSFileManager;
@class FIRCLSInternalReport;
@class FIRCLSSettings;
@class GDTCORTransport;
@class FIRInstallations;
@protocol FIRAnalyticsInterop;
@interface FIRCLSReportManager : NSObject
- (instancetype)initWithFileManager:(FIRCLSFileManager *)fileManager
installations:(FIRInstallations *)installations
analytics:(nullable id<FIRAnalyticsInterop>)analytics
googleAppID:(NSString *)googleAppID
dataArbiter:(FIRCLSDataCollectionArbiter *)dataArbiter
googleTransport:(GDTCORTransport *)googleTransport
appIDModel:(FIRCLSApplicationIdentifierModel *)appIDModel
settings:(FIRCLSSettings *)settings NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
- (FBLPromise<NSNumber *> *)startWithProfilingMark:(FIRCLSProfileMark)mark;
- (FBLPromise<NSNumber *> *)checkForUnsentReports;
- (FBLPromise *)sendUnsentReports;
- (FBLPromise *)deleteUnsentReports;
@end
extern NSString *const FIRCLSConfigSubmitReportsKey;
extern NSString *const FIRCLSConfigPackageReportsKey;
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,909 @@
// 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.
//
// The report manager has the ability to send to two different endpoints.
//
// The old legacy flow for a report goes through the following states/folders:
// 1. active - .clsrecords optimized for crash time persistence
// 2. processing - .clsrecords with attempted symbolication
// 3. prepared-legacy - .multipartmime of compressed .clsrecords
//
// The new flow for a report goes through the following states/folders:
// 1. active - .clsrecords optimized for crash time persistence
// 2. processing - .clsrecords with attempted symbolication
// 3. prepared - .clsrecords moved from processing with no changes
//
// The code was designed so the report processing workflows are not dramatically different from one
// another. The design will help avoid having a lot of conditional code blocks throughout the
// codebase.
//
#include <stdatomic.h>
#if __has_include(<FBLPromises/FBLPromises.h>)
#import <FBLPromises/FBLPromises.h>
#else
#import "FBLPromises.h"
#endif
#import "FIRCLSApplication.h"
#import "FIRCLSDataCollectionArbiter.h"
#import "FIRCLSDataCollectionToken.h"
#import "FIRCLSDefines.h"
#import "FIRCLSFeatures.h"
#import "FIRCLSFileManager.h"
#import "FIRCLSInternalReport.h"
#import "FIRCLSLogger.h"
#import "FIRCLSNetworkClient.h"
#import "FIRCLSPackageReportOperation.h"
#import "FIRCLSProcessReportOperation.h"
#import "FIRCLSReportUploader.h"
#import "FIRCLSSettings.h"
#import "FIRCLSSymbolResolver.h"
#import "FIRCLSUserLogging.h"
#include "FIRCLSGlobals.h"
#include "FIRCLSUtility.h"
#import "FIRCLSConstants.h"
#import "FIRCLSExecutionIdentifierModel.h"
#import "FIRCLSInstallIdentifierModel.h"
#import "FIRCLSSettingsOnboardingManager.h"
#import "FIRCLSReportManager_Private.h"
#import "Interop/Analytics/Public/FIRAnalyticsInterop.h"
#import "Interop/Analytics/Public/FIRAnalyticsInteropListener.h"
#include "FIRAEvent+Internal.h"
#include "FIRCLSFCRAnalytics.h"
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#else
#import <AppKit/AppKit.h>
#endif
static NSTimeInterval const CLSReportRetryInterval = 10 * 60;
static NSString *FIRCLSFirebaseAnalyticsEventLogFormat = @"$A$:%@";
@interface FIRCLSAnalyticsInteropListener : NSObject <FIRAnalyticsInteropListener> {
}
@end
@implementation FIRCLSAnalyticsInteropListener
- (void)messageTriggered:(NSString *)name parameters:(NSDictionary *)parameters {
NSDictionary *event = @{
@"name" : name,
@"parameters" : parameters,
};
NSString *json = FIRCLSFIRAEventDictionaryToJSON(event);
if (json != nil) {
FIRCLSLog(FIRCLSFirebaseAnalyticsEventLogFormat, json);
}
}
@end
/**
* A FIRReportAction is used to indicate how to handle unsent reports.
*/
typedef NS_ENUM(NSInteger, FIRCLSReportAction) {
/** Upload the reports to Crashlytics. */
FIRCLSReportActionSend,
/** Delete the reports without uploading them. */
FIRCLSReportActionDelete,
};
/**
* This is just a helper to make code using FIRReportAction more readable.
*/
typedef NSNumber FIRCLSWrappedReportAction;
@implementation NSNumber (FIRCLSWrappedReportAction)
- (FIRCLSReportAction)reportActionValue {
return [self intValue];
}
@end
/**
* This is a helper to make code using NSNumber for bools more readable.
*/
typedef NSNumber FIRCLSWrappedBool;
@interface FIRCLSReportManager () <FIRCLSNetworkClientDelegate,
FIRCLSReportUploaderDelegate,
FIRCLSReportUploaderDataSource> {
FIRCLSFileManager *_fileManager;
FIRCLSNetworkClient *_networkClient;
FIRCLSReportUploader *_uploader;
dispatch_queue_t _dispatchQueue;
NSOperationQueue *_operationQueue;
id<FIRAnalyticsInterop> _analytics;
// A promise that will be resolved when unsent reports are found on the device, and
// processReports: can be called to decide how to deal with them.
FBLPromise<FIRCLSWrappedBool *> *_unsentReportsAvailable;
// A promise that will be resolved when the user has provided an action that they want to perform
// for all the unsent reports.
FBLPromise<FIRCLSWrappedReportAction *> *_reportActionProvided;
// A promise that will be resolved when all unsent reports have been "handled". They won't
// necessarily have been uploaded, but we will know whether they should be sent or deleted, and
// the initial work to make that happen will have been processed on the work queue.
//
// Currently only used for testing
FBLPromise *_unsentReportsHandled;
// A token to make sure that checkForUnsentReports only gets called once.
atomic_bool _checkForUnsentReportsCalled;
BOOL _registeredAnalyticsEventListener;
}
@property(nonatomic, readonly) NSString *googleAppID;
@property(nonatomic, strong) FIRCLSDataCollectionArbiter *dataArbiter;
// Uniquely identifies a build / binary of the app
@property(nonatomic, strong) FIRCLSApplicationIdentifierModel *appIDModel;
// Uniquely identifies an install of the app
@property(nonatomic, strong) FIRCLSInstallIdentifierModel *installIDModel;
// Uniquely identifies a run of the app
@property(nonatomic, strong) FIRCLSExecutionIdentifierModel *executionIDModel;
// Settings fetched from the server
@property(nonatomic, strong) FIRCLSSettings *settings;
// Runs the operations that fetch settings and call onboarding endpoints
@property(nonatomic, strong) FIRCLSSettingsOnboardingManager *settingsAndOnboardingManager;
@property(nonatomic, strong) GDTCORTransport *googleTransport;
@end
@implementation FIRCLSReportManager
// Used only for internal data collection E2E testing
static void (^reportSentCallback)(void);
- (instancetype)initWithFileManager:(FIRCLSFileManager *)fileManager
installations:(FIRInstallations *)installations
analytics:(id<FIRAnalyticsInterop>)analytics
googleAppID:(NSString *)googleAppID
dataArbiter:(FIRCLSDataCollectionArbiter *)dataArbiter
googleTransport:(GDTCORTransport *)googleTransport
appIDModel:(FIRCLSApplicationIdentifierModel *)appIDModel
settings:(FIRCLSSettings *)settings {
self = [super init];
if (!self) {
return nil;
}
_fileManager = fileManager;
_analytics = analytics;
_googleAppID = [googleAppID copy];
_dataArbiter = dataArbiter;
_googleTransport = googleTransport;
NSString *sdkBundleID = FIRCLSApplicationGetSDKBundleID();
_operationQueue = [NSOperationQueue new];
[_operationQueue setMaxConcurrentOperationCount:1];
[_operationQueue setName:[sdkBundleID stringByAppendingString:@".work-queue"]];
_dispatchQueue = dispatch_queue_create("com.google.firebase.crashlytics.startup", 0);
_operationQueue.underlyingQueue = _dispatchQueue;
_networkClient = [self clientWithOperationQueue:_operationQueue];
_unsentReportsAvailable = [FBLPromise pendingPromise];
_reportActionProvided = [FBLPromise pendingPromise];
_unsentReportsHandled = [FBLPromise pendingPromise];
_checkForUnsentReportsCalled = NO;
_installIDModel = [[FIRCLSInstallIdentifierModel alloc] initWithInstallations:installations];
_executionIDModel = [[FIRCLSExecutionIdentifierModel alloc] init];
_settings = settings;
_appIDModel = appIDModel;
_settingsAndOnboardingManager =
[[FIRCLSSettingsOnboardingManager alloc] initWithAppIDModel:appIDModel
installIDModel:self.installIDModel
settings:self.settings
fileManager:self.fileManager
googleAppID:self.googleAppID];
return self;
}
- (FIRCLSNetworkClient *)clientWithOperationQueue:(NSOperationQueue *)queue {
return [[FIRCLSNetworkClient alloc] initWithQueue:queue fileManager:_fileManager delegate:self];
}
/**
* Returns the number of unsent reports on the device, including the ones passed in.
*/
- (int)unsentReportsCountWithPreexisting:(NSArray<NSString *> *)paths {
int count = [self countSubmittableAndDeleteUnsubmittableReportPaths:paths];
count += _fileManager.processingPathContents.count;
if (self.settings.shouldUseNewReportEndpoint) {
count += _fileManager.preparedPathContents.count;
} else {
count += _fileManager.legacyPreparedPathContents.count;
}
return count;
}
// This method returns a promise that is resolved with a wrapped FIRReportAction once the user has
// indicated whether they want to upload currently cached reports.
// This method should only be called when we have determined there is at least 1 unsent report.
// This method waits until either:
// 1. Data collection becomes enabled, in which case, the promise will be resolved with Send.
// 2. The developer uses the processCrashReports API to indicate whether the report
// should be sent or deleted, at which point the promise will be resolved with the action.
- (FBLPromise<FIRCLSWrappedReportAction *> *)waitForReportAction {
FIRCLSDebugLog(@"[Crashlytics:Crash] Notifying that unsent reports are available.");
[_unsentReportsAvailable fulfill:@YES];
// If data collection gets enabled while we are waiting for an action, go ahead and send the
// reports, and any subsequent explicit response will be ignored.
FBLPromise<FIRCLSWrappedReportAction *> *collectionEnabled =
[[self.dataArbiter waitForCrashlyticsCollectionEnabled]
then:^id _Nullable(NSNumber *_Nullable value) {
return @(FIRCLSReportActionSend);
}];
FIRCLSDebugLog(@"[Crashlytics:Crash] Waiting for send/deleteUnsentReports to be called.");
// Wait for either the processReports callback to be called, or data collection to be enabled.
return [FBLPromise race:@[ collectionEnabled, _reportActionProvided ]];
}
- (FBLPromise<FIRCLSWrappedBool *> *)checkForUnsentReports {
bool expectedCalled = NO;
if (!atomic_compare_exchange_strong(&_checkForUnsentReportsCalled, &expectedCalled, YES)) {
FIRCLSErrorLog(@"checkForUnsentReports should only be called once per execution.");
return [FBLPromise resolvedWith:@NO];
}
return _unsentReportsAvailable;
}
- (FBLPromise *)sendUnsentReports {
[_reportActionProvided fulfill:@(FIRCLSReportActionSend)];
return _unsentReportsHandled;
}
- (FBLPromise *)deleteUnsentReports {
[_reportActionProvided fulfill:@(FIRCLSReportActionDelete)];
return _unsentReportsHandled;
}
- (FBLPromise<NSNumber *> *)startWithProfilingMark:(FIRCLSProfileMark)mark {
NSString *executionIdentifier = self.executionIDModel.executionID;
// This needs to be called before any values are read from settings
NSTimeInterval currentTimestamp = [NSDate timeIntervalSinceReferenceDate];
[self.settings reloadFromCacheWithGoogleAppID:self.googleAppID currentTimestamp:currentTimestamp];
if (![self validateAppIdentifiers]) {
return [FBLPromise resolvedWith:@NO];
}
#if DEBUG
FIRCLSDebugLog(@"Root: %@", [_fileManager rootPath]);
#endif
if ([self.dataArbiter isLegacyDataCollectionKeyInPlist]) {
FIRCLSErrorLog(@"Found legacy data collection key in app's Info.plist: "
@"firebase_crashlytics_collection_enabled");
FIRCLSErrorLog(@"Please update your Info.plist to use the new data collection key: "
@"FirebaseCrashlyticsCollectionEnabled");
FIRCLSErrorLog(@"The legacy data collection Info.plist value could be overridden by "
@"calling: [Fabric with:...]");
FIRCLSErrorLog(@"The new value can be overridden by calling: [[FIRCrashlytics "
@"crashlytics] setCrashlyticsCollectionEnabled:<isEnabled>]");
return [FBLPromise resolvedWith:@NO];
}
if (![_fileManager createReportDirectories]) {
return [FBLPromise resolvedWith:@NO];
}
// Grab existing reports
BOOL launchFailure = [self checkForAndCreateLaunchMarker];
NSArray *preexistingReportPaths = _fileManager.activePathContents;
FIRCLSInternalReport *report = [self setupCurrentReport:executionIdentifier];
if (!report) {
FIRCLSErrorLog(@"Unable to setup a new report");
}
if (![self startCrashReporterWithProfilingMark:mark report:report]) {
FIRCLSErrorLog(@"Unable to start crash reporter");
report = nil;
}
// Regenerate the Install ID on a background thread if it needs to rotate because
// fetching the Firebase Install ID can be slow on some devices. This should happen after we
// create the session on disk so that we can update the Install ID in the written crash report
// metadata.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self checkAndRotateInstallUUIDIfNeededWithReport:report];
});
FBLPromise<NSNumber *> *promise = [FBLPromise resolvedWith:@(report != nil)];
if ([self.dataArbiter isCrashlyticsCollectionEnabled]) {
FIRCLSDebugLog(@"Automatic data collection is enabled.");
FIRCLSDebugLog(@"Unsent reports will be uploaded at startup");
FIRCLSDataCollectionToken *dataCollectionToken = [FIRCLSDataCollectionToken validToken];
[self beginSettingsAndOnboardingWithToken:dataCollectionToken waitForSettingsRequest:NO];
[self beginReportUploadsWithToken:dataCollectionToken
preexistingReportPaths:preexistingReportPaths
blockingSend:launchFailure
report:report];
// If data collection is enabled, the SDK will not notify the user
// when unsent reports are available, or respect Send / DeleteUnsentReports
[_unsentReportsAvailable fulfill:@NO];
} else {
FIRCLSDebugLog(@"Automatic data collection is disabled.");
// TODO: This counting of the file system happens on the main thread. Now that some of the other
// work below has been made async and moved to the dispatch queue, maybe we can move this code
// to the dispatch queue as well.
int unsentReportsCount = [self unsentReportsCountWithPreexisting:preexistingReportPaths];
if (unsentReportsCount > 0) {
FIRCLSDebugLog(
@"[Crashlytics:Crash] %d unsent reports are available. Checking for upload permission.",
unsentReportsCount);
// Wait for an action to get sent, either from processReports: or automatic data collection.
promise = [[self waitForReportAction]
onQueue:_dispatchQueue
then:^id _Nullable(FIRCLSWrappedReportAction *_Nullable wrappedAction) {
// Process the actions for the reports on disk.
FIRCLSReportAction action = [wrappedAction reportActionValue];
if (action == FIRCLSReportActionSend) {
FIRCLSDebugLog(@"Sending unsent reports.");
FIRCLSDataCollectionToken *dataCollectionToken =
[FIRCLSDataCollectionToken validToken];
// For the new report endpoint, the orgID is not needed.
// For the legacy report endpoint, wait on settings if orgID is not available.
BOOL waitForSetting =
!self.settings.shouldUseNewReportEndpoint && !self.settings.orgID;
[self beginSettingsAndOnboardingWithToken:dataCollectionToken
waitForSettingsRequest:waitForSetting];
[self beginReportUploadsWithToken:dataCollectionToken
preexistingReportPaths:preexistingReportPaths
blockingSend:NO
report:report];
} else if (action == FIRCLSReportActionDelete) {
FIRCLSDebugLog(@"Deleting unsent reports.");
[self deleteUnsentReportsWithPreexisting:preexistingReportPaths];
} else {
FIRCLSErrorLog(@"Unknown report action: %d", action);
}
return @(report != nil);
}];
} else {
FIRCLSDebugLog(@"[Crashlytics:Crash] There are no unsent reports.");
[_unsentReportsAvailable fulfill:@NO];
}
}
if (report != nil) {
// capture the start-up time here, but record it asynchronously
double endMark = FIRCLSProfileEnd(mark);
dispatch_async(FIRCLSGetLoggingQueue(), ^{
FIRCLSUserLoggingWriteInternalKeyValue(FIRCLSStartTimeKey, [@(endMark) description]);
});
}
// To make the code more predictable and therefore testable, don't resolve the startup promise
// until the operations that got queued up for processing reports have been processed through the
// work queue.
NSOperationQueue *__weak queue = _operationQueue;
FBLPromise *__weak unsentReportsHandled = _unsentReportsHandled;
promise = [promise then:^id _Nullable(NSNumber *_Nullable value) {
[queue waitUntilAllOperationsAreFinished];
// Signal that to callers of processReports that everything is finished.
[unsentReportsHandled fulfill:nil];
return value;
}];
return promise;
}
- (void)checkAndRotateInstallUUIDIfNeededWithReport:(FIRCLSInternalReport *)report {
[self.installIDModel regenerateInstallIDIfNeededWithBlock:^(BOOL didRotate) {
if (!didRotate) {
return;
}
FIRCLSContextUpdateMetadata(report, self.settings, self.installIDModel, self->_fileManager);
}];
}
- (void)beginSettingsAndOnboardingWithToken:(FIRCLSDataCollectionToken *)token
waitForSettingsRequest:(BOOL)waitForSettings {
if (self.settings.isCacheExpired) {
// This method can be called more than once if the user calls
// SendUnsentReports again, so don't repeat the settings fetch
static dispatch_once_t settingsFetchOnceToken;
dispatch_once(&settingsFetchOnceToken, ^{
[self.settingsAndOnboardingManager beginSettingsAndOnboardingWithGoogleAppId:self.googleAppID
token:token
waitForCompletion:waitForSettings];
});
}
}
- (void)beginReportUploadsWithToken:(FIRCLSDataCollectionToken *)token
preexistingReportPaths:(NSArray *)preexistingReportPaths
blockingSend:(BOOL)blockingSend
report:(FIRCLSInternalReport *)report {
if (self.settings.collectReportsEnabled) {
[self processExistingReportPaths:preexistingReportPaths
dataCollectionToken:token
asUrgent:blockingSend];
[self handleContentsInOtherReportingDirectoriesWithToken:token];
} else {
FIRCLSInfoLog(@"Collect crash reports is disabled");
[self deleteUnsentReportsWithPreexisting:preexistingReportPaths];
}
}
- (BOOL)startCrashReporterWithProfilingMark:(FIRCLSProfileMark)mark
report:(FIRCLSInternalReport *)report {
if (!report) {
return NO;
}
if (!FIRCLSContextInitialize(report, self.settings, self.installIDModel, _fileManager)) {
return NO;
}
[self setupStateNotifications];
[self registerAnalyticsEventListener];
[self crashReportingSetupCompleted:mark];
return YES;
}
- (void)crashReportingSetupCompleted:(FIRCLSProfileMark)mark {
// check our handlers
FIRCLSDispatchAfter(2.0, dispatch_get_main_queue(), ^{
FIRCLSExceptionCheckHandlers((__bridge void *)(self));
FIRCLSSignalCheckHandlers();
#if CLS_MACH_EXCEPTION_SUPPORTED
FIRCLSMachExceptionCheckHandlers();
#endif
});
// remove the launch failure marker and record the startup time
dispatch_async(dispatch_get_main_queue(), ^{
[self removeLaunchFailureMarker];
dispatch_async(FIRCLSGetLoggingQueue(), ^{
FIRCLSUserLoggingWriteInternalKeyValue(FIRCLSFirstRunloopTurnTimeKey,
[@(FIRCLSProfileEnd(mark)) description]);
});
});
}
- (BOOL)validateAppIdentifiers {
// When the ApplicationIdentifierModel fails to initialize, it is usually due to
// failing computeExecutableInfo. This can happen if the user sets the
// Exported Symbols File in Build Settings, and leaves off the one symbol
// that Crashlytics needs, "__mh_execute_header" (wich is defined in mach-o/ldsyms.h as
// _MH_EXECUTE_SYM). From https://github.com/firebase/firebase-ios-sdk/issues/5020
if (!self.appIDModel) {
FIRCLSErrorLog(
@"Crashlytics could not find the symbol for the app's main function and cannot "
@"start up. This can happen when Exported Symbols File is set in Build Settings. To "
@"resolve this, add \"__mh_execute_header\" as a newline to your Exported Symbols File.");
return NO;
}
if (self.appIDModel.bundleID.length == 0) {
FIRCLSErrorLog(@"An application must have a valid bundle identifier in its Info.plist");
return NO;
}
return YES;
}
- (FIRCLSReportUploader *)uploader {
if (!_uploader) {
_uploader = [[FIRCLSReportUploader alloc] initWithQueue:self.operationQueue
delegate:self
dataSource:self
client:self.networkClient
fileManager:_fileManager
analytics:_analytics];
}
return _uploader;
}
#pragma mark - Reporting Lifecycle
- (FIRCLSInternalReport *)setupCurrentReport:(NSString *)executionIdentifier {
[self createLaunchFailureMarker];
NSString *reportPath = [_fileManager setupNewPathForExecutionIdentifier:executionIdentifier];
return [[FIRCLSInternalReport alloc] initWithPath:reportPath
executionIdentifier:executionIdentifier];
}
- (int)countSubmittableAndDeleteUnsubmittableReportPaths:(NSArray *)reportPaths {
int count = 0;
for (NSString *path in reportPaths) {
FIRCLSInternalReport *report = [FIRCLSInternalReport reportWithPath:path];
if ([report needsToBeSubmitted]) {
count++;
} else {
[self.operationQueue addOperationWithBlock:^{
[self->_fileManager removeItemAtPath:path];
}];
}
}
return count;
}
- (void)processExistingReportPaths:(NSArray *)reportPaths
dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken
asUrgent:(BOOL)urgent {
for (NSString *path in reportPaths) {
[self processExistingActiveReportPath:path
dataCollectionToken:dataCollectionToken
asUrgent:urgent];
}
}
- (void)processExistingActiveReportPath:(NSString *)path
dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken
asUrgent:(BOOL)urgent {
FIRCLSInternalReport *report = [FIRCLSInternalReport reportWithPath:path];
// TODO: needsToBeSubmitted should really be called on the background queue.
if (![report needsToBeSubmitted]) {
[self.operationQueue addOperationWithBlock:^{
[self->_fileManager removeItemAtPath:path];
}];
return;
}
if (urgent && [dataCollectionToken isValid]) {
// We can proceed without the delegate.
[[self uploader] prepareAndSubmitReport:report
dataCollectionToken:dataCollectionToken
asUrgent:urgent
withProcessing:YES];
return;
}
[self submitReport:report dataCollectionToken:dataCollectionToken];
}
- (void)submitReport:(FIRCLSInternalReport *)report
dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken {
[self.operationQueue addOperationWithBlock:^{
[[self uploader] prepareAndSubmitReport:report
dataCollectionToken:dataCollectionToken
asUrgent:NO
withProcessing:YES];
}];
[self didSubmitReport];
}
// This is the side-effect of calling deleteUnsentReports, or collect_reports setting
// being false
- (void)deleteUnsentReportsWithPreexisting:(NSArray *)preexistingReportPaths {
[self removeExistingReportPaths:preexistingReportPaths];
[self removeExistingReportPaths:self.fileManager.processingPathContents];
if (self.settings.shouldUseNewReportEndpoint) {
[self removeExistingReportPaths:self.fileManager.preparedPathContents];
} else {
[self removeExistingReportPaths:self.fileManager.legacyPreparedPathContents];
}
}
- (void)removeExistingReportPaths:(NSArray *)reportPaths {
[self.operationQueue addOperationWithBlock:^{
for (NSString *path in reportPaths) {
[self.fileManager removeItemAtPath:path];
}
}];
}
- (void)handleContentsInOtherReportingDirectoriesWithToken:(FIRCLSDataCollectionToken *)token {
[self handleExistingFilesInProcessingWithToken:token];
[self handleExistingFilesInPreparedWithToken:token];
}
- (void)handleExistingFilesInProcessingWithToken:(FIRCLSDataCollectionToken *)token {
NSArray *processingPaths = _fileManager.processingPathContents;
// deal with stuff in processing more carefully - do not process again
[self.operationQueue addOperationWithBlock:^{
for (NSString *path in processingPaths) {
FIRCLSInternalReport *report = [FIRCLSInternalReport reportWithPath:path];
[[self uploader] prepareAndSubmitReport:report
dataCollectionToken:token
asUrgent:NO
withProcessing:NO];
}
}];
}
- (void)handleExistingFilesInPreparedWithToken:(FIRCLSDataCollectionToken *)token {
NSArray *preparedPaths = self.settings.shouldUseNewReportEndpoint
? _fileManager.preparedPathContents
: _fileManager.legacyPreparedPathContents;
// Give our network client a chance to reconnect here, if needed. This attempts to avoid
// trying to re-submit a prepared file that is already in flight.
[self.networkClient attemptToReconnectBackgroundSessionWithCompletionBlock:^{
[self.operationQueue addOperationWithBlock:^{
[self uploadPreexistingFiles:preparedPaths withToken:token];
}];
}];
}
- (void)uploadPreexistingFiles:(NSArray *)files withToken:(FIRCLSDataCollectionToken *)token {
// Because this could happen quite a bit after the inital set of files was
// captured, some could be completed (deleted). So, just double-check to make sure
// the file still exists.
for (NSString *path in files) {
if (![[_fileManager underlyingFileManager] fileExistsAtPath:path]) {
continue;
}
[[self uploader] uploadPackagedReportAtPath:path dataCollectionToken:token asUrgent:NO];
}
}
- (void)retryUploadForReportAtPath:(NSString *)path
dataCollectionToken:(FIRCLSDataCollectionToken *)token {
FIRCLSAddOperationAfter(CLSReportRetryInterval, self.operationQueue, ^{
FIRCLSDeveloperLog("Crashlytics:Crash", @"re-attempting report submission");
[[self uploader] uploadPackagedReportAtPath:path dataCollectionToken:token asUrgent:NO];
});
}
#pragma mark - Launch Failure Detection
- (NSString *)launchFailureMarkerPath {
return [[_fileManager structurePath] stringByAppendingPathComponent:@"launchmarker"];
}
- (BOOL)createLaunchFailureMarker {
// It's tempting to use - [NSFileManger createFileAtPath:contents:attributes:] here. But that
// operation, even with empty/nil contents does a ton of work to write out nothing via a
// temporarly file. This is a much faster implemenation.
const char *path = [[self launchFailureMarkerPath] fileSystemRepresentation];
#if TARGET_OS_IPHONE
/*
* data-protected non-portable open(2) :
* int open_dprotected_np(user_addr_t path, int flags, int class, int dpflags, int mode)
*/
int fd = open_dprotected_np(path, O_WRONLY | O_CREAT | O_TRUNC, 4, 0, 0644);
#else
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
#endif
if (fd == -1) {
return NO;
}
return close(fd) == 0;
}
- (BOOL)launchFailureMarkerPresent {
return [[_fileManager underlyingFileManager] fileExistsAtPath:[self launchFailureMarkerPath]];
}
- (BOOL)removeLaunchFailureMarker {
return [_fileManager removeItemAtPath:[self launchFailureMarkerPath]];
}
- (BOOL)checkForAndCreateLaunchMarker {
BOOL launchFailure = [self launchFailureMarkerPresent];
if (launchFailure) {
FIRCLSDeveloperLog("Crashlytics:Crash",
@"Last launch failed: this may indicate a crash shortly after app launch.");
} else {
[self createLaunchFailureMarker];
}
return launchFailure;
}
#pragma mark -
- (void)registerAnalyticsEventListener {
if (_registeredAnalyticsEventListener) {
return;
}
FIRCLSAnalyticsInteropListener *listener = [[FIRCLSAnalyticsInteropListener alloc] init];
[FIRCLSFCRAnalytics registerEventListener:listener toAnalytics:_analytics];
_registeredAnalyticsEventListener = YES;
}
#pragma mark - Notifications
- (void)setupStateNotifications {
[self captureInitialNotificationStates];
#if TARGET_OS_IOS
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(willBecomeActive:)
name:UIApplicationWillEnterForegroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didBecomeInactive:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didChangeOrientation:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(didChangeUIOrientation:)
name:UIApplicationDidChangeStatusBarOrientationNotification
object:nil];
#pragma clang diagnostic pop
#elif CLS_TARGET_OS_OSX
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(willBecomeActive:)
name:@"NSApplicationWillBecomeActiveNotification"
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didBecomeInactive:)
name:@"NSApplicationDidResignActiveNotification"
object:nil];
#endif
}
- (void)captureInitialNotificationStates {
#if TARGET_OS_IOS
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
UIInterfaceOrientation statusBarOrientation =
[FIRCLSApplicationSharedInstance() statusBarOrientation];
#endif
// It's nice to do this async, so we don't hold up the main thread while doing three
// consecutive IOs here.
dispatch_async(FIRCLSGetLoggingQueue(), ^{
FIRCLSUserLoggingWriteInternalKeyValue(FIRCLSInBackgroundKey, @"0");
#if TARGET_OS_IOS
FIRCLSUserLoggingWriteInternalKeyValue(FIRCLSDeviceOrientationKey,
[@(orientation) description]);
FIRCLSUserLoggingWriteInternalKeyValue(FIRCLSUIOrientationKey,
[@(statusBarOrientation) description]);
#endif
});
}
- (void)willBecomeActive:(NSNotification *)notification {
FIRCLSUserLoggingRecordInternalKeyValue(FIRCLSInBackgroundKey, @NO);
}
- (void)didBecomeInactive:(NSNotification *)notification {
FIRCLSUserLoggingRecordInternalKeyValue(FIRCLSInBackgroundKey, @YES);
}
#if TARGET_OS_IOS
- (void)didChangeOrientation:(NSNotification *)notification {
UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
FIRCLSUserLoggingRecordInternalKeyValue(FIRCLSDeviceOrientationKey, @(orientation));
}
- (void)didChangeUIOrientation:(NSNotification *)notification {
UIInterfaceOrientation statusBarOrientation =
[FIRCLSApplicationSharedInstance() statusBarOrientation];
FIRCLSUserLoggingRecordInternalKeyValue(FIRCLSUIOrientationKey, @(statusBarOrientation));
}
#endif
#pragma mark - FIRCLSNetworkClientDelegate
- (BOOL)networkClientCanUseBackgroundSessions:(FIRCLSNetworkClient *)client {
return !FIRCLSApplicationIsExtension();
}
- (void)networkClient:(FIRCLSNetworkClient *)client
didFinishUploadWithPath:(NSString *)path
error:(NSError *)error {
// Route this through to the reports uploader.
// Since this callback happens after an upload finished, then we can assume that the original data
// collection was authorized. This isn't ideal, but it's better than trying to plumb the data
// collection token through all the system networking callbacks.
FIRCLSDataCollectionToken *token = [FIRCLSDataCollectionToken validToken];
[[self uploader] reportUploadAtPath:path dataCollectionToken:token completedWithError:error];
}
#pragma mark - FIRCLSReportUploaderDelegate
- (void)didCompletePackageSubmission:(NSString *)path
dataCollectionToken:(FIRCLSDataCollectionToken *)token
error:(NSError *)error {
if (!error) {
FIRCLSDeveloperLog("Crashlytics:Crash", @"report submission successful");
return;
}
FIRCLSDeveloperLog("Crashlytics:Crash", @"report submission failed with error %@", error);
FIRCLSSDKLog("Error: failed to submit report '%s'\n", error.description.UTF8String);
[self retryUploadForReportAtPath:path dataCollectionToken:token];
}
- (void)didCompleteAllSubmissions {
[self.operationQueue addOperationWithBlock:^{
// Dealloc the reports uploader. If we need it again (if we re-enqueued submissions from
// didCompletePackageSubmission:, we can just create it again
self->_uploader = nil;
FIRCLSDeveloperLog("Crashlytics:Crash", @"report submission complete");
}];
}
#pragma mark - UITest Helpers
// Used only for internal data collection E2E testing
- (void)didSubmitReport {
if (reportSentCallback) {
dispatch_async(dispatch_get_main_queue(), ^{
reportSentCallback();
});
}
}
+ (void)setReportSentCallback:(void (^)(void))callback {
reportSentCallback = callback;
}
@end

View File

@ -0,0 +1,36 @@
// 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.
#import "FIRCLSReportManager.h"
#import "FIRCLSReportUploader.h"
@class FIRCLSInstallIdentifierModel;
@interface FIRCLSReportManager () <FIRCLSReportUploaderDelegate, FIRCLSReportUploaderDataSource>
@property(nonatomic, strong) NSOperationQueue *operationQueue;
@property(nonatomic, strong) FIRCLSNetworkClient *networkClient;
@property(nonatomic, readonly) FIRCLSReportUploader *uploader;
@property(nonatomic, strong) FIRCLSFileManager *fileManager;
@end
@interface FIRCLSReportManager (PrivateMethods)
- (BOOL)createLaunchFailureMarker;
- (BOOL)launchFailureMarkerPresent;
- (BOOL)potentiallySubmittableCrashOccurred;
@end

View File

@ -0,0 +1,80 @@
// 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.
#import <Foundation/Foundation.h>
#import <GoogleDataTransport/GDTCORTransport.h>
@class FIRCLSDataCollectionToken;
@class FIRCLSInternalReport;
@class FIRCLSSettings;
@class FIRCLSFileManager;
@class FIRCLSNetworkClient;
@class FIRCLSReportUploader;
@protocol FIRCLSReportUploaderDelegate;
@protocol FIRCLSReportUploaderDataSource;
@protocol FIRAnalyticsInterop;
@interface FIRCLSReportUploader : NSObject
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
- (instancetype)initWithQueue:(NSOperationQueue *)queue
delegate:(id<FIRCLSReportUploaderDelegate>)delegate
dataSource:(id<FIRCLSReportUploaderDataSource>)dataSource
client:(FIRCLSNetworkClient *)client
fileManager:(FIRCLSFileManager *)fileManager
analytics:(id<FIRAnalyticsInterop>)analytics NS_DESIGNATED_INITIALIZER;
@property(nonatomic, weak) id<FIRCLSReportUploaderDelegate> delegate;
@property(nonatomic, weak) id<FIRCLSReportUploaderDataSource> dataSource;
@property(nonatomic, readonly) NSOperationQueue *operationQueue;
@property(nonatomic, readonly) FIRCLSNetworkClient *networkClient;
@property(nonatomic, readonly) FIRCLSFileManager *fileManager;
- (BOOL)prepareAndSubmitReport:(FIRCLSInternalReport *)report
dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken
asUrgent:(BOOL)urgent
withProcessing:(BOOL)shouldProcess;
- (BOOL)uploadPackagedReportAtPath:(NSString *)path
dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken
asUrgent:(BOOL)urgent;
- (void)reportUploadAtPath:(NSString *)path
dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken
completedWithError:(NSError *)error;
@end
@protocol FIRCLSReportUploaderDelegate <NSObject>
@required
- (void)didCompletePackageSubmission:(NSString *)path
dataCollectionToken:(FIRCLSDataCollectionToken *)token
error:(NSError *)error;
- (void)didCompleteAllSubmissions;
@end
@protocol FIRCLSReportUploaderDataSource <NSObject>
@required
- (NSString *)googleAppID;
- (FIRCLSSettings *)settings;
- (GDTCORTransport *)googleTransport;
@end

View File

@ -0,0 +1,356 @@
// 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.
#import "Interop/Analytics/Public/FIRAnalyticsInterop.h"
#import "FIRCLSApplication.h"
#import "FIRCLSDataCollectionArbiter.h"
#import "FIRCLSDataCollectionToken.h"
#import "FIRCLSDefines.h"
#import "FIRCLSFCRAnalytics.h"
#import "FIRCLSFileManager.h"
#import "FIRCLSInstallIdentifierModel.h"
#import "FIRCLSInternalReport.h"
#import "FIRCLSNetworkClient.h"
#import "FIRCLSPackageReportOperation.h"
#import "FIRCLSProcessReportOperation.h"
#import "FIRCLSReportAdapter.h"
#import "FIRCLSReportUploader_Private.h"
#import "FIRCLSSettings.h"
#import "FIRCLSSymbolResolver.h"
#include "FIRCLSUtility.h"
#import "FIRCLSConstants.h"
#import "FIRCLSMultipartMimeStreamEncoder.h"
#import "FIRCLSURLBuilder.h"
#import <GoogleDataTransport/GDTCOREvent.h>
#import <GoogleDataTransport/GDTCORTransport.h>
@interface FIRCLSReportUploader () {
id<FIRAnalyticsInterop> _analytics;
}
@end
@implementation FIRCLSReportUploader
- (instancetype)initWithQueue:(NSOperationQueue *)queue
delegate:(id<FIRCLSReportUploaderDelegate>)delegate
dataSource:(id<FIRCLSReportUploaderDataSource>)dataSource
client:(FIRCLSNetworkClient *)client
fileManager:(FIRCLSFileManager *)fileManager
analytics:(id<FIRAnalyticsInterop>)analytics {
self = [super init];
if (!self) {
return nil;
}
_operationQueue = queue;
_delegate = delegate;
_dataSource = dataSource;
_networkClient = client;
_fileManager = fileManager;
_analytics = analytics;
return self;
}
#pragma mark - Packaging and Submission
- (BOOL)prepareAndSubmitReport:(FIRCLSInternalReport *)report
dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken
asUrgent:(BOOL)urgent
withProcessing:(BOOL)shouldProcess {
__block BOOL success = NO;
if (![dataCollectionToken isValid]) {
FIRCLSErrorLog(@"Data collection disabled and report will not be submitted");
return NO;
}
if (!self.dataSource.settings.orgID && !self.dataSource.settings.shouldUseNewReportEndpoint) {
FIRCLSDebugLog(
@"Skipping report with id '%@' this run of the app because Organization ID was "
@"nil. Report via the legacy endpoint will upload once settings are download successfully",
report.identifier);
return YES;
}
FIRCLSApplicationActivity(
FIRCLSApplicationActivityDefault, @"Crashlytics Crash Report Processing", ^{
if (shouldProcess) {
if (![self.fileManager moveItemAtPath:report.path
toDirectory:self.fileManager.processingPath]) {
FIRCLSErrorLog(@"Unable to move report for processing");
return;
}
// adjust the report's path, and process it
[report setPath:[self.fileManager.processingPath
stringByAppendingPathComponent:report.directoryName]];
FIRCLSSymbolResolver *resolver = [[FIRCLSSymbolResolver alloc] init];
FIRCLSProcessReportOperation *processOperation =
[[FIRCLSProcessReportOperation alloc] initWithReport:report resolver:resolver];
[processOperation start];
}
NSString *packagedPath;
FIRCLSDebugLog(@"Preparing the report for the new endpoint: %d",
self.dataSource.settings.shouldUseNewReportEndpoint);
// With the new report endpoint, the report is deleted once it is written to GDT
// Check if the report has a crash file before the report is moved or deleted
BOOL isCrash = report.isCrash;
if (self.dataSource.settings.shouldUseNewReportEndpoint) {
// For the new endpoint, just move the .clsrecords from "processing" -> "prepared"
if (![self.fileManager moveItemAtPath:report.path
toDirectory:self.fileManager.preparedPath]) {
FIRCLSErrorLog(@"Unable to move report to prepared");
return;
}
packagedPath = [self.fileManager.preparedPath
stringByAppendingPathComponent:report.path.lastPathComponent];
} else {
// For the legacy endpoint, continue generate the multipartmime file in "prepared-legacy"
FIRCLSPackageReportOperation *packageOperation =
[[FIRCLSPackageReportOperation alloc] initWithReport:report
fileManager:self.fileManager
settings:self.dataSource.settings];
[packageOperation start];
packagedPath = packageOperation.finalPath;
if (!packagedPath) {
FIRCLSErrorLog(@"Unable to package report");
return;
}
if (![self.fileManager removeItemAtPath:report.path]) {
FIRCLSErrorLog(@"Unable to remove a processing item");
}
}
NSLog(@"[Firebase/Crashlytics] Packaged report with id '%@' for submission",
report.identifier);
success = [self uploadPackagedReportAtPath:packagedPath
dataCollectionToken:dataCollectionToken
asUrgent:urgent];
// If the upload was successful and the report contained a crash forward it to Google
// Analytics.
if (success && isCrash) {
[FIRCLSFCRAnalytics logCrashWithTimeStamp:report.crashedOnDate.timeIntervalSince1970
toAnalytics:self->_analytics];
}
});
return success;
}
- (BOOL)submitPackageMultipartMimeAtPath:(NSString *)multipartmimePath
dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken
synchronously:(BOOL)synchronous {
FIRCLSDeveloperLog(@"Crashlytics:Crash:Reports", "Submitting %@ %@",
synchronous ? @"sync" : @"async", multipartmimePath);
if ([[[self fileManager] fileSizeAtPath:multipartmimePath] unsignedIntegerValue] == 0) {
FIRCLSDeveloperLog("Crashlytics:Crash:Reports", @"Already-submitted report being ignored");
return NO;
}
NSTimeInterval timeout = 10.0;
// If we are submitting synchronously, be more aggressive with the timeout. However,
// we only need this if the client does not support background requests.
if (synchronous && ![[self networkClient] supportsBackgroundRequests]) {
timeout = 2.0;
}
NSMutableURLRequest *request = [self mutableRequestWithURL:[self reportURL] timeout:timeout];
[request setHTTPMethod:@"POST"];
if (![self fillInRequest:request forMultipartMimeDataAtPath:multipartmimePath]) {
return NO;
}
[[self networkClient] startUploadRequest:request
filePath:multipartmimePath
dataCollectionToken:dataCollectionToken
immediately:synchronous];
return YES;
}
- (BOOL)uploadPackagedReportAtPath:(NSString *)path
dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken
asUrgent:(BOOL)urgent {
FIRCLSDeveloperLog("Crashlytics:Crash:Reports", @"Submitting report%@",
urgent ? @" as urgent" : @"");
// Check with the legacy path as the new path will always be contained in the legacy path
BOOL isNewPreparedPath = ![path containsString:self.fileManager.legacyPreparedPath];
if (isNewPreparedPath && self.dataSource.settings.shouldUseNewReportEndpoint) {
if (![dataCollectionToken isValid]) {
FIRCLSErrorLog(@"A report upload was requested with an invalid data collection token.");
return NO;
}
FIRCLSReportAdapter *adapter =
[[FIRCLSReportAdapter alloc] initWithPath:path googleAppId:self.dataSource.googleAppID];
GDTCOREvent *event = [self.dataSource.googleTransport eventForTransport];
event.dataObject = adapter;
event.qosTier = GDTCOREventQoSFast; // Bypass batching, send immediately
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block BOOL success = YES;
[self.dataSource.googleTransport
sendDataEvent:event
onComplete:^(BOOL wasWritten, NSError *error) {
if (!wasWritten) {
FIRCLSDeveloperLog("Crashlytics:Crash:Reports",
@"Failed to send crash report due to gdt write failure.");
success = NO;
return;
}
if (error) {
FIRCLSDeveloperLog("Crashlytics:Crash:Reports",
@"Failed to send crash report due to gdt error: %@",
error.localizedDescription);
success = NO;
return;
}
FIRCLSDeveloperLog("Crashlytics:Crash:Reports",
@"Completed report submission with id: %@", path.lastPathComponent);
if (urgent) {
dispatch_semaphore_signal(semaphore);
}
[self cleanUpSubmittedReportAtPath:path];
}];
if (urgent) {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
return success;
} else if (!isNewPreparedPath && !self.dataSource.settings.shouldUseNewReportEndpoint) {
return [self submitPackageMultipartMimeAtPath:path
dataCollectionToken:dataCollectionToken
synchronously:urgent];
}
// Unsupported state
return NO;
}
- (BOOL)cleanUpSubmittedReportAtPath:(NSString *)path {
if (![[self fileManager] removeItemAtPath:path]) {
FIRCLSErrorLog(@"Unable to remove packaged submission");
return NO;
}
return YES;
}
- (void)reportUploadAtPath:(NSString *)path
dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken
completedWithError:(NSError *)error {
FIRCLSDeveloperLog("Crashlytics:Crash:Reports", @"completed submission of %@", path);
if (!error) {
[self cleanUpSubmittedReportAtPath:path];
}
[[self delegate] didCompletePackageSubmission:path
dataCollectionToken:dataCollectionToken
error:error];
}
#pragma mark - Properties (TODO: Can delete once the experiment is over)
- (NSURL *)reportURL {
FIRCLSURLBuilder *url = [FIRCLSURLBuilder URLWithBase:FIRCLSReportsEndpoint];
[url appendComponent:@"/sdk-api/v1/platforms/"];
[url appendComponent:FIRCLSApplicationGetPlatform()];
[url appendComponent:@"/apps/"];
[url appendComponent:self.dataSource.settings.fetchedBundleID];
[url appendComponent:@"/reports"];
return [url URL];
}
- (NSString *)localeIdentifier {
return [[NSLocale currentLocale] localeIdentifier];
}
#pragma mark - URL Requests
- (NSMutableURLRequest *)mutableRequestWithURL:(NSURL *)url timeout:(NSTimeInterval)timeout {
NSMutableURLRequest *request =
[NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:timeout];
NSString *localeId = [self localeIdentifier];
[request setValue:@CLS_SDK_GENERATOR_NAME forHTTPHeaderField:FIRCLSNetworkUserAgent];
[request setValue:FIRCLSNetworkApplicationJson forHTTPHeaderField:FIRCLSNetworkAccept];
[request setValue:FIRCLSNetworkUTF8 forHTTPHeaderField:FIRCLSNetworkAcceptCharset];
[request setValue:localeId forHTTPHeaderField:FIRCLSNetworkAcceptLanguage];
[request setValue:localeId forHTTPHeaderField:FIRCLSNetworkContentLanguage];
[request setValue:FIRCLSDeveloperToken forHTTPHeaderField:FIRCLSNetworkCrashlyticsDeveloperToken];
[request setValue:FIRCLSApplicationGetSDKBundleID()
forHTTPHeaderField:FIRCLSNetworkCrashlyticsAPIClientId];
[request setValue:@CLS_SDK_DISPLAY_VERSION
forHTTPHeaderField:FIRCLSNetworkCrashlyticsAPIClientDisplayVersion];
[request setValue:[[self dataSource] googleAppID]
forHTTPHeaderField:FIRCLSNetworkCrashlyticsGoogleAppId];
return request;
}
- (BOOL)fillInRequest:(NSMutableURLRequest *)request forMultipartMimeDataAtPath:(NSString *)path {
NSString *boundary = [[path lastPathComponent] stringByDeletingPathExtension];
[request setValue:[FIRCLSMultipartMimeStreamEncoder
contentTypeHTTPHeaderValueWithBoundary:boundary]
forHTTPHeaderField:@"Content-Type"];
NSNumber *fileSize = [[self fileManager] fileSizeAtPath:path];
if (fileSize == nil) {
FIRCLSErrorLog(@"Could not determine size of multipart mime file");
return NO;
}
[request setValue:[fileSize stringValue] forHTTPHeaderField:@"Content-Length"];
return YES;
}
@end

View File

@ -0,0 +1,23 @@
// 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.
#import "FIRCLSReportUploader.h"
@interface FIRCLSReportUploader (PrivateMethods)
@property(nonatomic, readonly) NSURL *reportURL;
- (NSMutableURLRequest *)mutableRequestWithURL:(NSURL *)url timeout:(NSTimeInterval)timeout;
@end

View File

@ -0,0 +1,39 @@
// 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.
#import <Foundation/Foundation.h>
@class FIRApp;
@class FBLPromise<T>;
NS_ASSUME_NONNULL_BEGIN
@interface FIRCLSDataCollectionArbiter : NSObject
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithApp:(FIRApp *)app withAppInfo:(NSDictionary *)dict;
- (BOOL)isLegacyDataCollectionKeyInPlist;
- (BOOL)isCrashlyticsCollectionEnabled;
- (void)setCrashlyticsCollectionEnabled:(BOOL)enabled;
// Returns a promise that is fulfilled once data collection is enabled.
- (FBLPromise<NSNumber *> *)waitForCrashlyticsCollectionEnabled;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,148 @@
// 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.
#import "FIRCLSDataCollectionArbiter.h"
#if __has_include(<FBLPromises/FBLPromises.h>)
#import <FBLPromises/FBLPromises.h>
#else
#import "FBLPromises.h"
#endif
#import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h"
#import "FIRCLSUserDefaults.h"
// The legacy data collection setting allows Fabric customers to turn off auto-
// initialization, but can be overridden by calling [Fabric with:].
//
// While we support Fabric, we must have two different versions, because
// they require these slightly different semantics.
NSString *const FIRCLSLegacyCrashlyticsCollectionKey = @"firebase_crashlytics_collection_enabled";
// The new data collection setting can be set by an API that is stored in FIRCLSUserDefaults
NSString *const FIRCLSDataCollectionEnabledKey = @"com.crashlytics.data_collection";
// The new data collection setting also allows Firebase customers to turn off data
// collection in their Info.plist, and can be overridden by setting it to true using
// the setCrashlyticsCollectionEnabled API.
NSString *const FIRCLSCrashlyticsCollectionKey = @"FirebaseCrashlyticsCollectionEnabled";
typedef NS_ENUM(NSInteger, FIRCLSDataCollectionSetting) {
FIRCLSDataCollectionSettingNotSet = 0,
FIRCLSDataCollectionSettingEnabled = 1,
FIRCLSDataCollectionSettingDisabled = 2,
};
@interface FIRCLSDataCollectionArbiter () {
NSLock *_mutex;
FBLPromise *_dataCollectionEnabled;
BOOL _promiseResolved;
FIRApp *_app;
NSDictionary *_appInfo;
}
@end
@implementation FIRCLSDataCollectionArbiter
- (instancetype)initWithApp:(FIRApp *)app withAppInfo:(NSDictionary *)dict {
self = [super init];
if (self) {
_mutex = [[NSLock alloc] init];
_appInfo = dict;
_app = app;
if ([FIRCLSDataCollectionArbiter isCrashlyticsCollectionEnabledWithApp:app withAppInfo:dict]) {
_dataCollectionEnabled = [FBLPromise resolvedWith:nil];
_promiseResolved = YES;
} else {
_dataCollectionEnabled = [FBLPromise pendingPromise];
_promiseResolved = NO;
}
}
return self;
}
/*
* Legacy collection key that we provide for customers to disable Crash reporting.
* Customers can later turn on Crashlytics using Fabric.with if they choose to do so.
*
* This flag is unsupported for the "New SDK"
*/
- (BOOL)isLegacyDataCollectionKeyInPlist {
if ([_appInfo objectForKey:FIRCLSLegacyCrashlyticsCollectionKey]) {
return true;
}
return false;
}
// This functionality is called in the initializer before self is fully initialized,
// so a class method is used. The instance method below allows for a consistent clean API.
+ (BOOL)isCrashlyticsCollectionEnabledWithApp:(FIRApp *)app withAppInfo:(NSDictionary *)dict {
FIRCLSDataCollectionSetting stickySetting = [FIRCLSDataCollectionArbiter stickySetting];
if (stickySetting != FIRCLSDataCollectionSettingNotSet) {
return stickySetting == FIRCLSDataCollectionSettingEnabled;
}
id firebaseCrashlyticsCollectionEnabled = [dict objectForKey:FIRCLSCrashlyticsCollectionKey];
if ([firebaseCrashlyticsCollectionEnabled isKindOfClass:[NSString class]] ||
[firebaseCrashlyticsCollectionEnabled isKindOfClass:[NSNumber class]]) {
return [firebaseCrashlyticsCollectionEnabled boolValue];
}
return [app isDataCollectionDefaultEnabled];
}
- (BOOL)isCrashlyticsCollectionEnabled {
return [FIRCLSDataCollectionArbiter isCrashlyticsCollectionEnabledWithApp:_app
withAppInfo:_appInfo];
}
- (void)setCrashlyticsCollectionEnabled:(BOOL)enabled {
FIRCLSUserDefaults *userDefaults = [FIRCLSUserDefaults standardUserDefaults];
FIRCLSDataCollectionSetting setting =
enabled ? FIRCLSDataCollectionSettingEnabled : FIRCLSDataCollectionSettingDisabled;
[userDefaults setInteger:setting forKey:FIRCLSDataCollectionEnabledKey];
[userDefaults synchronize];
[_mutex lock];
if (enabled) {
if (!_promiseResolved) {
[_dataCollectionEnabled fulfill:nil];
_promiseResolved = YES;
}
} else {
if (_promiseResolved) {
_dataCollectionEnabled = [FBLPromise pendingPromise];
_promiseResolved = NO;
}
}
[_mutex unlock];
}
+ (FIRCLSDataCollectionSetting)stickySetting {
FIRCLSUserDefaults *userDefaults = [FIRCLSUserDefaults standardUserDefaults];
return [userDefaults integerForKey:FIRCLSDataCollectionEnabledKey];
}
- (FBLPromise *)waitForCrashlyticsCollectionEnabled {
FBLPromise *result = nil;
[_mutex lock];
result = _dataCollectionEnabled;
[_mutex unlock];
return result;
}
@end

View File

@ -0,0 +1,45 @@
// 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.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
* A FIRCLSDataCollectionToken represents having permission to upload data. A data collection token
* is either valid or nil. Every function that directly initiates a network operation that will
* result in data collection must check to make sure it has been passed a valid token. Tokens should
* only be created when either (1) automatic data collection is enabled, or (2) the user has
* explicitly given permission to collect data for a particular purpose, using the API. For all the
* functions in between, the data collection token getting passed as an argument helps to document
* and enforce the flow of data collection permission through the SDK.
*/
@interface FIRCLSDataCollectionToken : NSObject
/**
* Creates a valid token. Only call this method when either (1) automatic data collection is
* enabled, or (2) the user has explicitly given permission to collect data for a particular
* purpose, using the API.
*/
+ (instancetype)validToken;
/**
* Use this to verify that a token is valid. If this is called on a nil instance, it will return NO.
* @return YES.
*/
- (BOOL)isValid;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,27 @@
// 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.
#import "FIRCLSDataCollectionToken.h"
@implementation FIRCLSDataCollectionToken
+ (instancetype)validToken {
return [[FIRCLSDataCollectionToken alloc] init];
}
- (BOOL)isValid {
return YES;
}
@end

View File

@ -0,0 +1,84 @@
// 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.
#import "FIRCLSURLSessionAvailability.h"
#if FIRCLSURLSESSION_REQUIRED
#import "FIRCLSURLSessionConfiguration.h"
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface FIRCLSURLSession : NSObject {
id<NSURLSessionDelegate> _delegate;
NSOperationQueue *_delegateQueue;
NSURLSessionConfiguration *_configuration;
NSMutableSet *_taskSet;
dispatch_queue_t _queue;
NSString *_sessionDescription;
}
+ (BOOL)NSURLSessionShouldBeUsed;
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration
delegate:(nullable id<NSURLSessionDelegate>)delegate
delegateQueue:(nullable NSOperationQueue *)queue;
@property(nonatomic, readonly, retain) NSOperationQueue *delegateQueue;
@property(nonatomic, readonly, retain) id<NSURLSessionDelegate> delegate;
@property(nonatomic, readonly, copy) NSURLSessionConfiguration *configuration;
@property(nonatomic, copy) NSString *sessionDescription;
- (void)getTasksWithCompletionHandler:
(void (^)(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks))completionHandler;
// task creation - suitable for background operations
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL;
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request;
- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url;
// convenience methods (that are not available for background sessions
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
completionHandler:(nullable void (^)(NSData *data,
NSURLResponse *response,
NSError *error))completionHandler;
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request;
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
completionHandler:
(nullable void (^)(NSURL *targetPath,
NSURLResponse *response,
NSError *error))completionHandler;
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
fromFile:(NSURL *)fileURL
completionHandler:
(nullable void (^)(NSData *data,
NSURLResponse *response,
NSError *error))completionHandler;
- (void)invalidateAndCancel;
- (void)finishTasksAndInvalidate;
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -0,0 +1,346 @@
// 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.
#import "FIRCLSURLSessionAvailability.h"
#if FIRCLSURLSESSION_REQUIRED
#import "FIRCLSURLSession.h"
#import "FIRCLSURLSessionDataTask.h"
#import "FIRCLSURLSessionDataTask_PrivateMethods.h"
#import "FIRCLSURLSessionDownloadTask.h"
#import "FIRCLSURLSessionDownloadTask_PrivateMethods.h"
#import "FIRCLSURLSessionTask_PrivateMethods.h"
#import "FIRCLSURLSessionUploadTask.h"
#define DELEGATE ((id<NSURLSessionDataDelegate, NSURLSessionDownloadDelegate>)self->_delegate)
@interface FIRCLSURLSession () <FIRCLSURLSessionDownloadDelegate>
@property(nonatomic, retain) NSOperationQueue *delegateQueue;
@property(nonatomic, retain) id<NSURLSessionDelegate> delegate;
@property(nonatomic, copy) NSURLSessionConfiguration *configuration;
@end
@implementation FIRCLSURLSession
@synthesize delegate = _delegate;
@synthesize delegateQueue = _delegateQueue;
@synthesize configuration = _configuration;
@synthesize sessionDescription = _sessionDescription;
+ (BOOL)NSURLSessionShouldBeUsed {
if (!NSClassFromString(@"NSURLSession")) {
return NO;
}
// We use this as a proxy to verify that we are on at least iOS 8 or 10.10. The first OSes that
// has NSURLSession were fairly unstable.
return [[NSURLSessionConfiguration class]
respondsToSelector:@selector(backgroundSessionConfigurationWithIdentifier:)];
}
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration {
return [self sessionWithConfiguration:configuration delegate:nil delegateQueue:nil];
}
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration
delegate:(nullable id<NSURLSessionDelegate>)delegate
delegateQueue:(nullable NSOperationQueue *)queue {
if ([self NSURLSessionShouldBeUsed]) {
return [NSURLSession sessionWithConfiguration:configuration
delegate:delegate
delegateQueue:queue];
}
if (!configuration) {
return nil;
}
#if __has_feature(objc_arc)
FIRCLSURLSession *session = [self new];
#else
FIRCLSURLSession *session = [[self new] autorelease];
#endif
[session setDelegate:delegate];
// When delegate exists, but delegateQueue is nil, create a serial queue like NSURLSession
// documents.
if (delegate && !queue) {
queue = [self newDefaultDelegateQueue];
}
session.delegateQueue = queue;
session.configuration = configuration;
return (NSURLSession *)session;
}
+ (NSOperationQueue *)newDefaultDelegateQueue {
NSOperationQueue *delegateQueue = [[NSOperationQueue alloc] init];
delegateQueue.name = [NSString stringWithFormat:@"%@ %p", NSStringFromClass(self), self];
delegateQueue.maxConcurrentOperationCount = 1;
return delegateQueue;
}
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
_queue = dispatch_queue_create("com.crashlytics.URLSession", 0);
return self;
}
#if !__has_feature(objc_arc)
- (void)dealloc {
[_taskSet release];
[_delegate release];
[_delegateQueue release];
[_configuration release];
#if !OS_OBJECT_USE_OBJC
dispatch_release(_queue);
#endif
[super dealloc];
}
#endif
#pragma mark - Managing the Session
- (void)invalidateAndCancel {
dispatch_sync(_queue, ^{
for (FIRCLSURLSessionTask *task in self->_taskSet) {
[task cancel];
}
});
self.delegate = nil;
}
- (void)finishTasksAndInvalidate {
self.delegate = nil;
}
#pragma mark -
- (void)getTasksWithCompletionHandler:
(void (^)(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks))completionHandler {
[[self delegateQueue] addOperationWithBlock:^{
// TODO - this is totally wrong, but better than not calling back at all
completionHandler(@[], @[], @[]);
}];
}
- (void)removeTaskFromSet:(FIRCLSURLSessionTask *)task {
dispatch_async(_queue, ^{
[self->_taskSet removeObject:task];
});
}
- (void)configureTask:(FIRCLSURLSessionTask *)task
withRequest:(NSURLRequest *)request
block:(void (^)(NSMutableURLRequest *mutableRequest))block {
NSMutableURLRequest *modifiedRequest = [request mutableCopy];
dispatch_sync(_queue, ^{
[self->_taskSet addObject:task];
// TODO: this isn't allowed to overwrite existing headers
for (NSString *key in [self->_configuration HTTPAdditionalHeaders]) {
[modifiedRequest addValue:[[self->_configuration HTTPAdditionalHeaders] objectForKey:key]
forHTTPHeaderField:key];
}
});
if (block) {
block(modifiedRequest);
}
[task setOriginalRequest:modifiedRequest];
[task setDelegate:self];
#if !__has_feature(objc_arc)
[modifiedRequest release];
#endif
}
- (BOOL)shouldInvokeDelegateSelector:(SEL)selector forTask:(FIRCLSURLSessionTask *)task {
return [task invokesDelegate] && [_delegate respondsToSelector:selector];
}
#pragma mark Task Creation
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
fromFile:(NSURL *)fileURL {
return [self uploadTaskWithRequest:request fromFile:fileURL completionHandler:nil];
}
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request {
return [self downloadTaskWithRequest:request completionHandler:nil];
}
- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url {
return [self downloadTaskWithRequest:[NSURLRequest requestWithURL:url]];
}
#pragma mark Async Convenience Methods
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
completionHandler:(nullable void (^)(NSData *data,
NSURLResponse *response,
NSError *error))completionHandler {
FIRCLSURLSessionDataTask *task = [FIRCLSURLSessionDataTask task];
if (completionHandler) {
[task setCompletionHandler:completionHandler];
[task setInvokesDelegate:NO];
}
[self configureTask:task withRequest:request block:nil];
return (NSURLSessionDataTask *)task;
}
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request {
return [self dataTaskWithRequest:request completionHandler:nil];
}
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
fromFile:(NSURL *)fileURL
completionHandler:
(nullable void (^)(NSData *data,
NSURLResponse *response,
NSError *error))completionHandler {
FIRCLSURLSessionUploadTask *task = [FIRCLSURLSessionUploadTask task];
if (completionHandler) {
[task setCompletionHandler:completionHandler];
[task setInvokesDelegate:NO];
}
[self configureTask:task
withRequest:request
block:^(NSMutableURLRequest *mutableRequest) {
// you cannot set up both of these, and we'll be using the stream here
[mutableRequest setHTTPBody:nil];
[mutableRequest setHTTPBodyStream:[NSInputStream inputStreamWithURL:fileURL]];
}];
return (NSURLSessionUploadTask *)task;
}
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
completionHandler:
(nullable void (^)(NSURL *targetPath,
NSURLResponse *response,
NSError *error))completionHandler {
FIRCLSURLSessionDownloadTask *task = [FIRCLSURLSessionDownloadTask task];
if (completionHandler) {
[task setDownloadCompletionHandler:completionHandler];
[task setInvokesDelegate:NO];
}
[self configureTask:task withRequest:request block:nil];
return (NSURLSessionDownloadTask *)task;
}
#pragma mark FIRCLSURLSessionTaskDelegate
- (NSURLRequest *)task:(FIRCLSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
newRequest:(NSURLRequest *)request {
// just accept the proposed redirection
return request;
}
- (void)task:(FIRCLSURLSessionTask *)task didCompleteWithError:(NSError *)error {
if (![self shouldInvokeDelegateSelector:@selector(URLSession:task:didCompleteWithError:)
forTask:task]) {
[self removeTaskFromSet:task];
return;
}
[_delegateQueue addOperationWithBlock:^{
[DELEGATE URLSession:(NSURLSession *)self
task:(NSURLSessionTask *)task
didCompleteWithError:error];
// Note that you *cannot* clean up here, because this method could be run asynchronously with
// the delegate methods that care about the state of the task
[self removeTaskFromSet:task];
}];
}
#pragma mark FIRCLSURLSessionDataTask
- (void)task:(FIRCLSURLSessionDataTask *)task didReceiveResponse:(NSURLResponse *)response {
if (![self shouldInvokeDelegateSelector:@selector
(URLSession:dataTask:didReceiveResponse:completionHandler:)
forTask:task]) {
return;
}
[_delegateQueue addOperationWithBlock:^{
[DELEGATE URLSession:(NSURLSession *)self
dataTask:(NSURLSessionDataTask *)task
didReceiveResponse:response
completionHandler:^(NSURLSessionResponseDisposition disposition){
// nothing to do here
}];
}];
}
- (void)task:(FIRCLSURLSessionDataTask *)task didReceiveData:(NSData *)data {
if (![self shouldInvokeDelegateSelector:@selector(URLSession:dataTask:didReceiveData:)
forTask:task]) {
return;
}
[_delegateQueue addOperationWithBlock:^{
[DELEGATE URLSession:(NSURLSession *)self
dataTask:(NSURLSessionDataTask *)task
didReceiveData:data];
}];
}
#pragma mark FIRCLSURLSessionDownloadDelegate
- (void)downloadTask:(FIRCLSURLSessionDownloadTask *)task didFinishDownloadingToURL:(NSURL *)url {
if (![self shouldInvokeDelegateSelector:@selector(URLSession:
downloadTask:didFinishDownloadingToURL:)
forTask:task]) {
// We have to be certain that we cleanup only once the delegate no longer cares about the state
// of the task being changed. In the case of download, this is either after the delegate method
// has been invoked, or here, if the delegate doesn't care.
[task cleanup];
return;
}
[_delegateQueue addOperationWithBlock:^{
[DELEGATE URLSession:(NSURLSession *)self
downloadTask:(NSURLSessionDownloadTask *)task
didFinishDownloadingToURL:url];
// Cleanup for the download tasks is a little complex. As long as we do it only after
// the delegate has been informed of the completed download, we are ok.
[task cleanup];
}];
}
@end
#else
INJECT_STRIP_SYMBOL(clsurlsession)
#endif

View File

@ -0,0 +1,28 @@
// 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.
#pragma once
#include <Foundation/Foundation.h>
#define FIRCLSURLSESSION_REQUIRED (!TARGET_OS_WATCH && !TARGET_OS_TV)
// These macros generate a function to force a symbol for the containing .o, to work around an issue
// where strip will not strip debug information without a symbol to strip.
#define CONCAT_EXPANDED(a, b) a##b
#define CONCAT(a, b) CONCAT_EXPANDED(a, b)
#define DUMMY_FUNCTION_NAME(x) CONCAT(fircls_strip_this_, x)
#define INJECT_STRIP_SYMBOL(x) \
void DUMMY_FUNCTION_NAME(x)(void) { \
}

View File

@ -0,0 +1,42 @@
// 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.
#import "FIRCLSURLSessionAvailability.h"
#if FIRCLSURLSESSION_REQUIRED
#import <Foundation/Foundation.h>
@interface FIRCLSURLSessionConfiguration : NSObject <NSCopying> {
NSDictionary *_additionalHeaders;
NSURLCache *_URLCache;
NSHTTPCookieAcceptPolicy _cookiePolicy;
}
+ (NSURLSessionConfiguration *)defaultSessionConfiguration;
+ (NSURLSessionConfiguration *)ephemeralSessionConfiguration;
+ (NSURLSessionConfiguration *)backgroundSessionConfiguration:(NSString *)identifier;
+ (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:(NSString *)identifier;
@property(nonatomic, copy) NSDictionary *HTTPAdditionalHeaders;
@property(nonatomic, retain) NSURLCache *URLCache;
@property(nonatomic, assign) NSHTTPCookieAcceptPolicy HTTPCookieAcceptPolicy;
@property(nonatomic, assign) BOOL sessionSendsLaunchEvents;
@property(nonatomic, assign) NSTimeInterval timeoutIntervalForRequest;
@property(nonatomic, assign) NSTimeInterval timeoutIntervalForResource;
@property(nonatomic, assign) BOOL allowsCellularAccess;
@end
#endif

View File

@ -0,0 +1,92 @@
// 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.
#import "FIRCLSURLSessionAvailability.h"
#import "FIRCLSURLSession.h"
#if FIRCLSURLSESSION_REQUIRED
#import "FIRCLSURLSessionConfiguration.h"
@implementation FIRCLSURLSessionConfiguration
@synthesize URLCache = _URLCache;
@synthesize HTTPAdditionalHeaders = _additionalHeaders;
@synthesize HTTPCookieAcceptPolicy = _cookiePolicy;
+ (NSURLSessionConfiguration *)defaultSessionConfiguration {
if ([FIRCLSURLSession NSURLSessionShouldBeUsed]) {
return [NSURLSessionConfiguration defaultSessionConfiguration];
}
#if __has_feature(objc_arc)
return [self new];
#else
return [[self new] autorelease];
#endif
}
+ (NSURLSessionConfiguration *)ephemeralSessionConfiguration {
if ([FIRCLSURLSession NSURLSessionShouldBeUsed]) {
return [NSURLSessionConfiguration ephemeralSessionConfiguration];
}
#if __has_feature(objc_arc)
return [self new];
#else
return [[self new] autorelease];
#endif
}
+ (NSURLSessionConfiguration *)backgroundSessionConfiguration:(NSString *)identifier {
return [self backgroundSessionConfigurationWithIdentifier:identifier];
}
+ (NSURLSessionConfiguration *)backgroundSessionConfigurationWithIdentifier:(NSString *)identifier {
if (![FIRCLSURLSession NSURLSessionShouldBeUsed]) {
return nil;
}
if ([[NSURLSessionConfiguration class]
respondsToSelector:@selector(backgroundSessionConfigurationWithIdentifier:)]) {
return [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:identifier];
}
return [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:identifier];
}
- (id)copyWithZone:(NSZone *)zone {
FIRCLSURLSessionConfiguration *configuration;
configuration = [FIRCLSURLSessionConfiguration new];
[configuration setHTTPAdditionalHeaders:[self HTTPAdditionalHeaders]];
return configuration;
}
// This functionality is not supported by the wrapper, so we just stub it out
- (BOOL)sessionSendsLaunchEvents {
return NO;
}
- (void)setSessionSendsLaunchEvents:(BOOL)sessionSendsLaunchEvents {
}
@end
#else
INJECT_STRIP_SYMBOL(clsurlsessionconfiguration)
#endif

View File

@ -1,4 +1,4 @@
// Copyright 2020 Google LLC
// 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.
@ -12,11 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// An umbrella header, for any other libraries in this repo to access Firebase Public and Private
// headers. Any package manager complexity should be handled here.
#import "FIRCLSURLSessionAvailability.h"
#if FIRCLSURLSESSION_REQUIRED
#import <Foundation/Foundation.h>
@interface FIRCLSURLSession (PrivateMethods)
- (void)runOnDelegateQueue:(void (^)(void))block;
@end
#if SWIFT_PACKAGE
@import GoogleDataTransport;
#else
#import <GoogleDataTransport/GoogleDataTransport.h>
#endif

View File

@ -0,0 +1,32 @@
// 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.
#import "FIRCLSURLSessionAvailability.h"
#if FIRCLSURLSESSION_REQUIRED
#import "FIRCLSURLSessionTask.h"
@interface FIRCLSURLSessionDataTask : FIRCLSURLSessionTask {
void (^_completionHandler)(NSData *data, NSURLResponse *response, NSError *error);
NSURLConnection *_connection;
NSMutableData *_data;
NSString *_taskDescription;
}
@property(nonatomic, copy) NSString *taskDescription;
@end
#endif

View File

@ -0,0 +1,124 @@
// 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.
#import "FIRCLSURLSessionAvailability.h"
#if FIRCLSURLSESSION_REQUIRED
#import "FIRCLSURLSessionDataTask.h"
#import "FIRCLSURLSessionDataTask_PrivateMethods.h"
#define DELEGATE ((id<FIRCLSURLSessionDataDelegate>)[self delegate])
@interface FIRCLSURLSessionDataTask () <NSURLConnectionDelegate>
@end
@implementation FIRCLSURLSessionDataTask
@synthesize connection = _connection;
@synthesize completionHandler = _completionHandler;
@synthesize taskDescription = _taskDescription;
#if !__has_feature(objc_arc)
- (void)dealloc {
[_connection release];
[_completionHandler release];
[_taskDescription release];
[_data release];
[super dealloc];
}
#endif
- (void)resume {
dispatch_async([self queue], ^{
NSURLConnection *connection;
if ([self connection]) {
return;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
connection = [[NSURLConnection alloc] initWithRequest:[self originalRequest]
delegate:self
startImmediately:NO];
#pragma clang diagnostic pop
[self setConnection:connection];
// bummer we have to do this on a runloop, but other mechanisms require iOS 5 or 10.7
[connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
#if !__has_feature(objc_arc)
[connection release];
#endif
[connection start];
});
}
- (void)complete {
// call completion handler first
if (_completionHandler) {
// this should go to another queue
_completionHandler(_data, [self response], [self error]);
}
// and then finally, call the session delegate completion
[DELEGATE task:self didCompleteWithError:[self error]];
}
- (void)cancel {
[self.connection cancel];
}
#pragma mark NSURLConnectionDelegate
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
dispatch_async([self queue], ^{
[DELEGATE task:self didReceiveResponse:response];
[self setResponse:response];
});
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
dispatch_async([self queue], ^{
if (!self->_data) {
self->_data = [NSMutableData new];
}
[self->_data appendData:data];
[DELEGATE task:self didReceiveData:data];
});
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
dispatch_async([self queue], ^{
[self setError:error];
[self complete];
});
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
dispatch_async([self queue], ^{
[self complete];
});
}
@end
#else
INJECT_STRIP_SYMBOL(clsurlsessiondatatask)
#endif

View File

@ -0,0 +1,43 @@
// 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.
#import "FIRCLSURLSessionAvailability.h"
#if FIRCLSURLSESSION_REQUIRED
#import <Foundation/Foundation.h>
#import "FIRCLSURLSessionTask_PrivateMethods.h"
@protocol FIRCLSURLSessionDataDelegate;
@interface FIRCLSURLSessionDataTask ()
@property(nonatomic, retain) NSURLConnection *connection;
@property(nonatomic, copy) void (^completionHandler)
(NSData *data, NSURLResponse *response, NSError *error);
- (void)complete;
@end
@protocol FIRCLSURLSessionDataDelegate <FIRCLSURLSessionTaskDelegate>
@required
- (void)task:(FIRCLSURLSessionDataTask *)task didReceiveResponse:(NSURLResponse *)response;
- (void)task:(FIRCLSURLSessionDataTask *)task didReceiveData:(NSData *)data;
@end
#endif

View File

@ -0,0 +1,31 @@
// 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.
#import "FIRCLSURLSessionAvailability.h"
#if FIRCLSURLSESSION_REQUIRED
#import "FIRCLSURLSessionDataTask.h"
@protocol FIRCLSURLSessionDownloadDelegate;
@interface FIRCLSURLSessionDownloadTask : FIRCLSURLSessionDataTask {
void (^_downloadCompletionHandler)(NSURL *targetPath, NSURLResponse *response, NSError *error);
NSOutputStream *_outputStream;
NSURL *_targetURL;
}
@end
#endif

View File

@ -0,0 +1,157 @@
// 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.
#import "FIRCLSURLSessionAvailability.h"
#if FIRCLSURLSESSION_REQUIRED
#import "FIRCLSURLSessionDownloadTask.h"
#import "FIRCLSURLSessionDownloadTask_PrivateMethods.h"
#define DELEGATE ((id<FIRCLSURLSessionDownloadDelegate>)[self delegate])
@interface FIRCLSURLSessionDownloadTask () <NSStreamDelegate>
@end
@implementation FIRCLSURLSessionDownloadTask
@synthesize downloadCompletionHandler = _downloadCompletionHandler;
- (id)init {
self = [super init];
if (!self) {
return nil;
}
#if __has_feature(objc_arc)
_targetURL = [self temporaryFileURL];
_outputStream = [NSOutputStream outputStreamWithURL:_targetURL append:NO];
#else
_targetURL = [[self temporaryFileURL] retain];
_outputStream = [[NSOutputStream outputStreamWithURL:_targetURL append:NO] retain];
#endif
[_outputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
[_outputStream setDelegate:self];
return self;
}
#if !__has_feature(objc_arc)
- (void)dealloc {
[_downloadCompletionHandler release];
[_targetURL release];
[_outputStream release];
[super dealloc];
}
#else
- (void)dealloc {
[_outputStream close];
_outputStream.delegate = nil;
}
#endif
- (NSURL *)temporaryFileURL {
NSString *tmpPath;
tmpPath = [NSTemporaryDirectory()
stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
// TODO: make this actually unique
return [NSURL fileURLWithPath:tmpPath isDirectory:NO];
}
- (void)cleanup {
// now, remove the temporary file
[[NSFileManager defaultManager] removeItemAtURL:_targetURL error:nil];
}
- (void)complete {
// This is an override of FIRCLSURLSessionDataTask's cleanup method
// call completion handler first
if (_downloadCompletionHandler) {
_downloadCompletionHandler(_targetURL, [self response], [self error]);
}
// followed by the session delegate, if there was no error
if (![self error]) {
[DELEGATE downloadTask:self didFinishDownloadingToURL:_targetURL];
}
// and then finally, call the session delegate completion
[DELEGATE task:self didCompleteWithError:[self error]];
}
- (void)writeDataToStream:(NSData *)data {
// open the stream first
if ([_outputStream streamStatus] == NSStreamStatusNotOpen) {
[_outputStream open];
}
if ([data respondsToSelector:@selector(enumerateByteRangesUsingBlock:)]) {
[data enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) {
[self->_outputStream write:bytes maxLength:byteRange.length];
}];
return;
}
// fall back to the less-efficient mechanism for older OSes
[_outputStream write:[data bytes] maxLength:[data length]];
}
#pragma mark NSURLConnectionDelegate
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
dispatch_async([self queue], ^{
[self writeDataToStream:data];
});
}
- (void)completeForError {
dispatch_async([self queue], ^{
[self->_outputStream close];
[self->_connection cancel];
if (![self error]) {
[self setError:[NSError errorWithDomain:@"FIRCLSURLSessionDownloadTaskError"
code:-1
userInfo:nil]];
}
[self complete];
});
}
#pragma mark NSStreamDelegate
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
switch (eventCode) {
case NSStreamEventHasSpaceAvailable:
break;
case NSStreamEventErrorOccurred:
[self completeForError];
break;
case NSStreamEventEndEncountered:
break;
default:
break;
}
}
@end
#else
INJECT_STRIP_SYMBOL(clsurlsessiondownloadtask)
#endif

View File

@ -0,0 +1,39 @@
// 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.
#import "FIRCLSURLSessionAvailability.h"
#if FIRCLSURLSESSION_REQUIRED
#import <Foundation/Foundation.h>
#import "FIRCLSURLSessionDataTask_PrivateMethods.h"
@protocol FIRCLSURLSessionDownloadDelegate;
@interface FIRCLSURLSessionDownloadTask ()
@property(nonatomic, copy) void (^downloadCompletionHandler)
(NSURL *targetPath, NSURLResponse *response, NSError *error);
@end
@protocol FIRCLSURLSessionDownloadDelegate <FIRCLSURLSessionDataDelegate>
@required
- (void)downloadTask:(FIRCLSURLSessionDownloadTask *)task didFinishDownloadingToURL:(NSURL *)url;
@end
#endif

View File

@ -0,0 +1,38 @@
// 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.
#import <Foundation/Foundation.h>
@protocol FIRCLSURLSessionTaskDelegate;
@interface FIRCLSURLSessionTask : NSObject {
__unsafe_unretained id<FIRCLSURLSessionTaskDelegate> _delegate;
NSURLRequest* _originalRequest;
NSURLRequest* _currentRequest;
NSURLResponse* _response;
NSError* _error;
dispatch_queue_t _queue;
BOOL _invokesDelegate;
}
@property(nonatomic, readonly, copy) NSURLRequest* originalRequest;
@property(nonatomic, readonly, copy) NSURLRequest* currentRequest;
@property(nonatomic, readonly, copy) NSURLResponse* response;
@property(nonatomic, readonly, copy) NSError* error;
- (void)resume;
@end

View File

@ -0,0 +1,95 @@
// 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.
#import "FIRCLSURLSessionAvailability.h"
#if FIRCLSURLSESSION_REQUIRED
#import "FIRCLSURLSession.h"
#import "FIRCLSURLSessionTask.h"
#import "FIRCLSURLSessionTask_PrivateMethods.h"
#import "FIRCLSURLSession_PrivateMethods.h"
@implementation FIRCLSURLSessionTask
+ (instancetype)task {
#if __has_feature(objc_arc)
return [[self class] new];
#else
return [[[self class] new] autorelease];
#endif
}
@synthesize currentRequest = _currentRequest;
@synthesize originalRequest = _originalRequest;
@synthesize response = _response;
@synthesize error = _error;
@synthesize queue = _queue;
@synthesize invokesDelegate = _invokesDelegate;
- (instancetype)init {
self = [super init];
if (!self) {
return self;
}
_queue = dispatch_queue_create("com.crashlytics.URLSessionTask", 0);
_invokesDelegate = YES;
return self;
}
#if !__has_feature(objc_arc)
- (void)dealloc {
[_originalRequest release];
[_currentRequest release];
[_response release];
[_error release];
#if !OS_OBJECT_USE_OBJC
dispatch_release(_queue);
#endif
[super dealloc];
}
#endif
- (void)start {
#if DEBUG
assert(0 && "Must be implemented by FIRCLSURLSessionTask subclasses");
#endif
}
- (void)cancel {
#if DEBUG
assert(0 && "Must be implemented by FIRCLSURLSessionTask subclasses");
#endif
}
- (void)resume {
}
- (void)cleanup {
}
@end
#else
INJECT_STRIP_SYMBOL(clsurlsessiontask)
#endif

View File

@ -0,0 +1,55 @@
// 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.
#import "FIRCLSURLSessionAvailability.h"
#if FIRCLSURLSESSION_REQUIRED
#import <Foundation/Foundation.h>
@protocol FIRCLSURLSessionTaskDelegate;
@interface FIRCLSURLSessionTask ()
+ (instancetype)task;
@property(nonatomic, assign) id<FIRCLSURLSessionTaskDelegate> delegate;
@property(nonatomic, copy) NSURLRequest *originalRequest;
@property(nonatomic, copy) NSURLRequest *currentRequest;
@property(nonatomic, copy) NSURLResponse *response;
@property(nonatomic, readonly) dispatch_queue_t queue;
@property(nonatomic, assign) BOOL invokesDelegate;
- (void)cancel;
@property(nonatomic, copy) NSError *error;
- (void)cleanup;
@end
@protocol FIRCLSURLSessionTaskDelegate <NSObject>
@required
- (NSURLRequest *)task:(FIRCLSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
newRequest:(NSURLRequest *)request;
- (void)task:(FIRCLSURLSessionTask *)task didCompleteWithError:(NSError *)error;
@end
#endif

View File

@ -0,0 +1,25 @@
// 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.
#import "FIRCLSURLSessionAvailability.h"
#if FIRCLSURLSESSION_REQUIRED
#import "FIRCLSURLSessionDataTask.h"
@interface FIRCLSURLSessionUploadTask : FIRCLSURLSessionDataTask
@end
#endif

View File

@ -1,4 +1,4 @@
// Copyright 2020 Google LLC
// 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.
@ -12,11 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// An umbrella header, for any other libraries in this repo to access Firebase Public and Private
// headers. Any package manager complexity should be handled here.
#import "FIRCLSURLSessionAvailability.h"
#if FIRCLSURLSESSION_REQUIRED
#import "FIRCLSURLSessionUploadTask.h"
@implementation FIRCLSURLSessionUploadTask
@end
#if SWIFT_PACKAGE
@import GoogleDataTransport;
#else
#import <GoogleDataTransport/GoogleDataTransport.h>
INJECT_STRIP_SYMBOL(clsurlsessionuploadtask)
#endif

Some files were not shown because too many files have changed in this diff Show More