<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 }