<lambda>null1 package leakcanary.internal.activity.db
2
3 import android.content.ContentValues
4 import android.content.Context
5 import android.database.sqlite.SQLiteDatabase
6 import android.database.sqlite.SQLiteOpenHelper
7 import leakcanary.internal.Serializables
8 import leakcanary.internal.toByteArray
9 import shark.HeapAnalysis
10 import shark.HeapAnalysisSuccess
11 import shark.LeakTrace
12
13 internal class LeaksDbHelper(context: Context) : SQLiteOpenHelper(
14 context, DATABASE_NAME, null, VERSION
15 ) {
16
17 override fun onCreate(db: SQLiteDatabase) {
18 db.execSQL(HeapAnalysisTable.create)
19 db.execSQL(LeakTable.create)
20 db.execSQL(LeakTable.createSignatureIndex)
21 db.execSQL(LeakTraceTable.create)
22 }
23
24 override fun onUpgrade(
25 db: SQLiteDatabase,
26 oldVersion: Int,
27 newVersion: Int
28 ) {
29 if (oldVersion < 23) {
30 recreateDb(db)
31 return
32 }
33 if (oldVersion < 24) {
34 db.execSQL("ALTER TABLE heap_analysis ADD COLUMN dump_duration_millis INTEGER DEFAULT -1")
35 }
36 if (oldVersion < 25) {
37 // Fix owningClassName=null in the serialized heap analysis.
38 // https://github.com/square/leakcanary/issues/2067
39 val idToAnalysis = db.rawQuery("SELECT id, object FROM heap_analysis", null)
40 .use { cursor ->
41 generateSequence {
42 if (cursor.moveToNext()) {
43 val id = cursor.getLong(0)
44 val analysis = Serializables.fromByteArray<HeapAnalysis>(cursor.getBlob(1))
45 id to analysis
46 } else {
47 null
48 }
49 }
50 .filter {
51 it.second is HeapAnalysisSuccess
52 }
53 .map { pair ->
54 val analysis = pair.second as HeapAnalysisSuccess
55
56 val unreachableObjects = try {
57 analysis.unreachableObjects
58 } catch (ignored: NullPointerException) {
59 // This currently doesn't trigger but the Kotlin compiler might change one day.
60 emptyList()
61 } ?: emptyList() // Compiler doesn't know it but runtime can have null.
62 pair.first to analysis.copy(
63 unreachableObjects = unreachableObjects,
64 applicationLeaks = analysis.applicationLeaks.map { leak ->
65 leak.copy(leak.leakTraces.fixNullReferenceOwningClassName())
66 },
67 libraryLeaks = analysis.libraryLeaks.map { leak ->
68 leak.copy(leak.leakTraces.fixNullReferenceOwningClassName())
69 }
70 )
71 }.toList()
72 }
73 db.inTransaction {
74 idToAnalysis.forEach { (id, heapAnalysis) ->
75 val values = ContentValues()
76 values.put("object", heapAnalysis.toByteArray())
77 db.update("heap_analysis", values, "id=$id", null)
78 }
79 }
80 }
81 }
82
83 private fun List<LeakTrace>.fixNullReferenceOwningClassName(): List<LeakTrace> {
84 return map { leakTrace ->
85 leakTrace.copy(
86 referencePath = leakTrace.referencePath.map { reference ->
87 val owningClassName = try {
88 // This can return null at runtime from previous serialized version without the field.
89 reference.owningClassName
90 } catch (ignored: NullPointerException) {
91 // This currently doesn't trigger but the Kotlin compiler might change one day.
92 null
93 }
94 if (owningClassName == null) {
95 reference.copy(owningClassName = reference.originObject.classSimpleName)
96 } else {
97 reference
98 }
99 })
100 }
101 }
102
103 override fun onDowngrade(
104 db: SQLiteDatabase,
105 oldVersion: Int,
106 newVersion: Int
107 ) {
108 recreateDb(db)
109 }
110
111 private fun recreateDb(db: SQLiteDatabase) {
112 db.execSQL(HeapAnalysisTable.drop)
113 db.execSQL(LeakTable.drop)
114 db.execSQL(LeakTraceTable.drop)
115 onCreate(db)
116 }
117
118 companion object {
119 internal const val VERSION = 25
120 internal const val DATABASE_NAME = "leaks.db"
121 }
122 }