Skip to content

Commit 80ae437

Browse files
authored
Merge branch 'master' into davidmotson.pin_versions
2 parents a1addbe + b010838 commit 80ae437

File tree

15 files changed

+247
-32
lines changed

15 files changed

+247
-32
lines changed

firebase-crashlytics-ndk/firebase-crashlytics-ndk.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,10 @@ android {
2828
}
2929

3030
ndkVersion "25.1.8937393"
31-
compileSdkVersion project.targetSdkVersion
31+
compileSdkVersion 33
3232
defaultConfig {
3333
minSdkVersion 16
34-
targetSdkVersion project.targetSdkVersion
34+
targetSdkVersion 33
3535
versionName version
3636

3737
externalNativeBuild {

firebase-crashlytics-ndk/src/main/java/com/google/firebase/crashlytics/ndk/CrashpadController.java

Lines changed: 118 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,39 @@
1414

1515
package com.google.firebase.crashlytics.ndk;
1616

17+
import android.app.ActivityManager;
18+
import android.app.ApplicationExitInfo;
1719
import android.content.Context;
20+
import android.os.Build;
1821
import androidx.annotation.NonNull;
1922
import androidx.annotation.Nullable;
23+
import androidx.annotation.RequiresApi;
24+
import androidx.annotation.VisibleForTesting;
2025
import com.google.firebase.crashlytics.internal.Logger;
2126
import com.google.firebase.crashlytics.internal.common.CommonUtils;
27+
import com.google.firebase.crashlytics.internal.model.CrashlyticsReport;
2228
import com.google.firebase.crashlytics.internal.model.StaticSessionData;
2329
import com.google.firebase.crashlytics.internal.persistence.FileStore;
2430
import java.io.BufferedWriter;
31+
import java.io.ByteArrayOutputStream;
2532
import java.io.File;
2633
import java.io.FileOutputStream;
2734
import java.io.IOException;
35+
import java.io.InputStream;
2836
import java.io.OutputStreamWriter;
2937
import java.nio.charset.Charset;
38+
import java.util.ArrayList;
39+
import java.util.Base64;
40+
import java.util.List;
41+
import java.util.zip.GZIPOutputStream;
3042

3143
public class CrashpadController {
3244

3345
@SuppressWarnings("CharsetObjectCanBeUsed") // StandardCharsets requires API level 19.
3446
private static final Charset UTF_8 = Charset.forName("UTF-8");
3547

48+
private static final String SESSION_START_TIMESTAMP_FILE_NAME = "start-time";
49+
3650
private static final String SESSION_METADATA_FILE = "session.json";
3751
private static final String APP_METADATA_FILE = "app.json";
3852
private static final String DEVICE_METADATA_FILE = "device.json";
@@ -70,8 +84,8 @@ public boolean initialize(
7084
}
7185

7286
public boolean hasCrashDataForSession(String sessionId) {
73-
File crashFile = getFilesForSession(sessionId).minidump;
74-
return crashFile != null && crashFile.exists();
87+
SessionFiles files = getFilesForSession(sessionId);
88+
return files.nativeCore != null && files.nativeCore.hasCore();
7589
}
7690

7791
@NonNull
@@ -94,7 +108,7 @@ public SessionFiles getFilesForSession(String sessionId) {
94108
&& sessionFileDirectory.exists()
95109
&& sessionFileDirectoryForMinidump.exists()) {
96110
builder
97-
.minidumpFile(getSingleFileWithExtension(sessionFileDirectoryForMinidump, ".dmp"))
111+
.nativeCore(getNativeCore(sessionId, sessionFileDirectoryForMinidump))
98112
.metadataFile(getSingleFileWithExtension(sessionFileDirectory, ".device_info"))
99113
.sessionFile(new File(sessionFileDirectory, SESSION_METADATA_FILE))
100114
.appFile(new File(sessionFileDirectory, APP_METADATA_FILE))
@@ -104,6 +118,52 @@ public SessionFiles getFilesForSession(String sessionId) {
104118
return builder.build();
105119
}
106120

121+
private SessionFiles.NativeCore getNativeCore(
122+
String sessionId, File sessionFileDirectoryForMinidump) {
123+
File minidump = getSingleFileWithExtension(sessionFileDirectoryForMinidump, ".dmp");
124+
CrashlyticsReport.ApplicationExitInfo applicationExitInfo = getApplicationExitInfo(sessionId);
125+
return new SessionFiles.NativeCore(minidump, applicationExitInfo);
126+
}
127+
128+
private CrashlyticsReport.ApplicationExitInfo getApplicationExitInfo(String sessionId) {
129+
return android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
130+
? getNativeCrashApplicationExitInfo(sessionId)
131+
: null;
132+
}
133+
134+
@RequiresApi(api = Build.VERSION_CODES.S)
135+
private CrashlyticsReport.ApplicationExitInfo getNativeCrashApplicationExitInfo(
136+
String sessionId) {
137+
ActivityManager activityManager =
138+
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
139+
List<ApplicationExitInfo> applicationExitInfoList =
140+
activityManager.getHistoricalProcessExitReasons(null, 0, 0);
141+
142+
File sessionStartFile = fileStore.getSessionFile(sessionId, SESSION_START_TIMESTAMP_FILE_NAME);
143+
long sessionTime =
144+
sessionStartFile == null ? System.currentTimeMillis() : sessionStartFile.lastModified();
145+
146+
return getRelevantApplicationExitInfo(sessionTime, applicationExitInfoList);
147+
}
148+
149+
@RequiresApi(api = Build.VERSION_CODES.S)
150+
private CrashlyticsReport.ApplicationExitInfo getRelevantApplicationExitInfo(
151+
long sessionTime, List<ApplicationExitInfo> applicationExitInfoList) {
152+
List<ApplicationExitInfo> filtered = new ArrayList<>();
153+
for (ApplicationExitInfo applicationExitInfo : applicationExitInfoList) {
154+
if (applicationExitInfo.getReason() != ApplicationExitInfo.REASON_CRASH_NATIVE
155+
|| applicationExitInfo.getTimestamp() < sessionTime) {
156+
continue;
157+
}
158+
159+
filtered.add(applicationExitInfo);
160+
}
161+
162+
// For GWP-ASan and MTE, there can only be one tombstone, even in the case of non-crashy
163+
// GWP-ASan.
164+
return filtered.isEmpty() ? null : convertApplicationExitInfoToModel(filtered.get(0));
165+
}
166+
107167
public void writeBeginSession(String sessionId, String generator, long startedAtSeconds) {
108168
final String json =
109169
SessionMetadataJsonSerializer.serializeBeginSession(sessionId, generator, startedAtSeconds);
@@ -181,4 +241,59 @@ private static File getSingleFileWithExtension(File directory, String extension)
181241

182242
return null;
183243
}
244+
245+
@RequiresApi(api = Build.VERSION_CODES.S)
246+
private static CrashlyticsReport.ApplicationExitInfo convertApplicationExitInfoToModel(
247+
ApplicationExitInfo applicationExitInfo) {
248+
return CrashlyticsReport.ApplicationExitInfo.builder()
249+
.setImportance(applicationExitInfo.getImportance())
250+
.setProcessName(applicationExitInfo.getProcessName())
251+
.setReasonCode(applicationExitInfo.getReason())
252+
.setTimestamp(applicationExitInfo.getTimestamp())
253+
.setPid(applicationExitInfo.getPid())
254+
.setPss(applicationExitInfo.getPss())
255+
.setRss(applicationExitInfo.getRss())
256+
.setTraceFile(getTraceFileFromApplicationExitInfo(applicationExitInfo))
257+
.build();
258+
}
259+
260+
@RequiresApi(api = Build.VERSION_CODES.S)
261+
private static String getTraceFileFromApplicationExitInfo(
262+
ApplicationExitInfo applicationExitInfo) {
263+
try {
264+
return convertInputStreamToString(applicationExitInfo.getTraceInputStream());
265+
} catch (IOException e) {
266+
Logger.getLogger().w("Failed to get input stream from ApplicationExitInfo");
267+
}
268+
269+
return null;
270+
}
271+
272+
@VisibleForTesting
273+
@RequiresApi(api = Build.VERSION_CODES.S)
274+
public static String convertInputStreamToString(InputStream inputStream) throws IOException {
275+
if (inputStream == null) {
276+
return null;
277+
}
278+
279+
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
280+
byte[] bytes = new byte[8192];
281+
int length;
282+
while ((length = inputStream.read(bytes)) != -1) {
283+
byteArrayOutputStream.write(bytes, 0, length);
284+
}
285+
286+
return zipAndEncode(byteArrayOutputStream.toByteArray());
287+
}
288+
289+
@RequiresApi(api = Build.VERSION_CODES.S)
290+
private static String zipAndEncode(byte[] bytes) throws IOException {
291+
try (ByteArrayOutputStream out = new ByteArrayOutputStream();
292+
GZIPOutputStream gzip = new GZIPOutputStream(out)) {
293+
gzip.write(bytes);
294+
gzip.finish();
295+
296+
return Base64.getEncoder().encodeToString(out.toByteArray());
297+
}
298+
}
184299
}

firebase-crashlytics-ndk/src/main/java/com/google/firebase/crashlytics/ndk/SessionFiles.java

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,47 @@
1414

1515
package com.google.firebase.crashlytics.ndk;
1616

17+
import androidx.annotation.Nullable;
18+
import com.google.firebase.crashlytics.internal.model.CrashlyticsReport;
1719
import java.io.File;
1820

1921
final class SessionFiles {
22+
/**
23+
* Starting with Android S, it is possible to collect the tombstone upon an application restart.
24+
* In most cases, both, the tombstone and the minidump will be available.
25+
*/
26+
static final class NativeCore {
27+
@Nullable public final File minidump;
28+
29+
@Nullable public final CrashlyticsReport.ApplicationExitInfo applicationExitInfo;
30+
31+
NativeCore(
32+
@Nullable File minidump,
33+
@Nullable CrashlyticsReport.ApplicationExitInfo applicationExitInfo) {
34+
this.minidump = minidump;
35+
this.applicationExitInfo = applicationExitInfo;
36+
}
37+
38+
boolean hasCore() {
39+
// The classic case is that a crash occurred and a minidump was successfully captured. There
40+
// are two new cases to handle, however. First, a crash occurred and a minidump was not
41+
// captured - this is ok; check for a tombstone and use that instead. Second, a crash did not
42+
// occur because of a non-crashy GWP-ASan tombstone - this ok; capture just the tombstone.
43+
return (minidump != null && minidump.exists()) || applicationExitInfo != null;
44+
}
45+
}
2046

2147
static final class Builder {
22-
private File minidump;
48+
private NativeCore nativeCore;
2349
private File binaryImages;
2450
private File metadata;
2551
private File session;
2652
private File app;
2753
private File device;
2854
private File os;
2955

30-
Builder minidumpFile(File minidump) {
31-
this.minidump = minidump;
56+
Builder nativeCore(NativeCore nativeCore) {
57+
this.nativeCore = nativeCore;
3258
return this;
3359
}
3460

@@ -67,7 +93,7 @@ SessionFiles build() {
6793
}
6894
}
6995

70-
public final File minidump;
96+
public final NativeCore nativeCore;
7197
public final File binaryImages;
7298
public final File metadata;
7399
public final File session;
@@ -76,7 +102,7 @@ SessionFiles build() {
76102
public final File os;
77103

78104
private SessionFiles(Builder builder) {
79-
minidump = builder.minidump;
105+
nativeCore = builder.nativeCore;
80106
binaryImages = builder.binaryImages;
81107
metadata = builder.metadata;
82108
session = builder.session;

firebase-crashlytics-ndk/src/main/java/com/google/firebase/crashlytics/ndk/SessionFilesProvider.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package com.google.firebase.crashlytics.ndk;
1616

1717
import com.google.firebase.crashlytics.internal.NativeSessionFileProvider;
18+
import com.google.firebase.crashlytics.internal.model.CrashlyticsReport;
1819
import java.io.File;
1920

2021
class SessionFilesProvider implements NativeSessionFileProvider {
@@ -27,7 +28,12 @@ class SessionFilesProvider implements NativeSessionFileProvider {
2728

2829
@Override
2930
public File getMinidumpFile() {
30-
return sessionFiles.minidump;
31+
return sessionFiles.nativeCore.minidump;
32+
}
33+
34+
@Override
35+
public CrashlyticsReport.ApplicationExitInfo getApplicationExitInto() {
36+
return sessionFiles.nativeCore != null ? sessionFiles.nativeCore.applicationExitInfo : null;
3137
}
3238

3339
@Override

firebase-crashlytics/firebase-crashlytics.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ android {
2929
timeOutInMs 60 * 1000
3030
}
3131

32-
compileSdkVersion 30
32+
compileSdkVersion 33
3333
testOptions.unitTests.includeAndroidResources = true
3434
defaultConfig {
3535
minSdkVersion 16
36-
targetSdkVersion 30
36+
targetSdkVersion 33
3737
versionName version
3838

3939
multiDexEnabled true

firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/common/CrashlyticsControllerTest.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import com.google.firebase.crashlytics.internal.NativeSessionFileProvider;
3939
import com.google.firebase.crashlytics.internal.analytics.AnalyticsEventLogger;
4040
import com.google.firebase.crashlytics.internal.metadata.LogFileManager;
41+
import com.google.firebase.crashlytics.internal.model.CrashlyticsReport;
4142
import com.google.firebase.crashlytics.internal.persistence.FileStore;
4243
import com.google.firebase.crashlytics.internal.settings.Settings;
4344
import com.google.firebase.crashlytics.internal.settings.SettingsProvider;
@@ -239,6 +240,11 @@ public File getMinidumpFile() {
239240
return minidump;
240241
}
241242

243+
@Override
244+
public CrashlyticsReport.ApplicationExitInfo getApplicationExitInto() {
245+
return null;
246+
}
247+
242248
@Override
243249
public File getBinaryImagesFile() {
244250
return null;
@@ -279,9 +285,9 @@ public File getOsFile() {
279285

280286
controller.finalizeSessions(testSettingsProvider);
281287
verify(mockSessionReportingCoordinator)
282-
.finalizeSessionWithNativeEvent(eq(previousSessionId), any());
288+
.finalizeSessionWithNativeEvent(eq(previousSessionId), any(), any());
283289
verify(mockSessionReportingCoordinator, never())
284-
.finalizeSessionWithNativeEvent(eq(sessionId), any());
290+
.finalizeSessionWithNativeEvent(eq(sessionId), any(), any());
285291
}
286292

287293
public void testMissingNativeComponentCausesNoReports() {

firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/common/SessionReportingCoordinatorTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -397,11 +397,12 @@ public void testFinalizeSessionWithNativeEvent_createsCrashlyticsReportWithNativ
397397
String byteBackedSessionName = "byte";
398398
BytesBackedNativeSessionFile byteSession =
399399
new BytesBackedNativeSessionFile(byteBackedSessionName, "not_applicable", testBytes);
400-
reportingCoordinator.finalizeSessionWithNativeEvent("id", Arrays.asList(byteSession));
400+
reportingCoordinator.finalizeSessionWithNativeEvent("id", Arrays.asList(byteSession), null);
401401

402402
ArgumentCaptor<CrashlyticsReport.FilesPayload> filesPayload =
403403
ArgumentCaptor.forClass(CrashlyticsReport.FilesPayload.class);
404-
verify(reportPersistence).finalizeSessionWithNativeEvent(eq("id"), filesPayload.capture());
404+
verify(reportPersistence)
405+
.finalizeSessionWithNativeEvent(eq("id"), filesPayload.capture(), any());
405406
CrashlyticsReport.FilesPayload ndkPayloadFinalized = filesPayload.getValue();
406407
assertEquals(1, ndkPayloadFinalized.getFiles().size());
407408

firebase-crashlytics/src/androidTest/java/com/google/firebase/crashlytics/internal/persistence/CrashlyticsReportPersistenceTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -350,11 +350,11 @@ public void testFinalizeReports_prioritizesNativeAndNonnativeFatals() throws IOE
350350
fileStore, createSettingsProviderMock(4, VERY_LARGE_UPPER_LIMIT));
351351

352352
persistReportWithEvent(reportPersistence, "testSession1", true);
353-
reportPersistence.finalizeSessionWithNativeEvent("testSession1", filesPayload);
353+
reportPersistence.finalizeSessionWithNativeEvent("testSession1", filesPayload, null);
354354
persistReportWithEvent(reportPersistence, "testSession2low", false);
355355
persistReportWithEvent(reportPersistence, "testSession3low", false);
356356
persistReportWithEvent(reportPersistence, "testSession4", true);
357-
reportPersistence.finalizeSessionWithNativeEvent("testSession4", filesPayload);
357+
reportPersistence.finalizeSessionWithNativeEvent("testSession4", filesPayload, null);
358358
reportPersistence.finalizeReports("skippedSession", 0L);
359359

360360
List<CrashlyticsReportWithSessionId> finalizedReports =
@@ -453,7 +453,7 @@ public void testFinalizeSessionWithNativeEvent_writesNativeSessions() {
453453

454454
assertEquals(0, finalizedReports.size());
455455

456-
reportPersistence.finalizeSessionWithNativeEvent("sessionId", filesPayload);
456+
reportPersistence.finalizeSessionWithNativeEvent("sessionId", filesPayload, null);
457457

458458
finalizedReports = reportPersistence.loadFinalizedReports();
459459
assertEquals(1, finalizedReports.size());

firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/CrashlyticsNativeComponentDeferredProxy.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package com.google.firebase.crashlytics.internal;
1616

1717
import androidx.annotation.NonNull;
18+
import com.google.firebase.crashlytics.internal.model.CrashlyticsReport;
1819
import com.google.firebase.crashlytics.internal.model.StaticSessionData;
1920
import com.google.firebase.inject.Deferred;
2021
import java.io.File;
@@ -85,6 +86,11 @@ public File getMinidumpFile() {
8586
return null;
8687
}
8788

89+
@Override
90+
public CrashlyticsReport.ApplicationExitInfo getApplicationExitInto() {
91+
return null;
92+
}
93+
8894
@Override
8995
public File getBinaryImagesFile() {
9096
return null;

firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/NativeSessionFileProvider.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package com.google.firebase.crashlytics.internal;
1616

1717
import androidx.annotation.Nullable;
18+
import com.google.firebase.crashlytics.internal.model.CrashlyticsReport;
1819
import java.io.File;
1920

2021
/** Provides references to the files that make up a native crash report. */
@@ -25,6 +26,9 @@ public interface NativeSessionFileProvider {
2526
@Nullable
2627
File getMinidumpFile();
2728

29+
@Nullable
30+
CrashlyticsReport.ApplicationExitInfo getApplicationExitInto();
31+
2832
@Nullable
2933
File getBinaryImagesFile();
3034

0 commit comments

Comments
 (0)