main

square/leakcanary

Last updated at: 29/12/2023 09:39

StringPathFinderOptimTest.kt

TLDR

This file contains a test class, StringPathFinderOptimTest, which tests the path finding algorithm for strings. The algorithm avoids unnecessary reads by skipping the content of strings. The file includes three test methods that test different scenarios for the reachability of String#value on different Android versions.

Methods

findStringContent(hprofFile: File): HeapAnalysisSuccess

This private method takes a File parameter representing the HPROF file to be analyzed. It uses a HeapAnalyzer to perform the analysis on the given heap dump file. The method finds all instances of the java.lang.String class and retrieves the value field of each instance. It then returns the result of the analysis as a HeapAnalysisSuccess object.

Classes

StringPathFinderOptimTest

This test class contains three test methods that test the findStringContent method in different scenarios. The tests check the reachability of String#value on different Android versions. The class imports org.assertj.core.api.Assertions.assertThat and java.io.File.

package shark

import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import java.io.File

/**
 * Our path finding algorithm skips going through the content of strings to avoid unnecessary reads.
 * We add back the corresponding size when computing the shallow size of retained strings,
 * however that only works if those byte arrays aren't reachable through other references.
 * If they were, this could either inflate the retained size number (byte array should not be
 * considered retained) or deflate it (byte array counted twice when reached from two retained
 * instances).
 */
class StringPathFinderOptimTest {

  @Test fun `String#value not reachable on Android O`() {
    val hprofFile = "leak_asynctask_o.hprof".classpathFile()
    val analysis = findStringContent(hprofFile)
    assertThat(analysis.allLeaks.count()).isEqualTo(0)
  }

  @Test fun `String#value not reachable on Android M`() {
    val hprofFile = "leak_asynctask_m.hprof".classpathFile()
    val analysis = findStringContent(hprofFile)
    assertThat(analysis.allLeaks.count()).isEqualTo(0)
  }

  @Test fun `String#value only reachable for String#ASCII pre Android M`() {
    val hprofFile = "leak_asynctask_pre_m.hprof".classpathFile()
    val analysis = findStringContent(hprofFile)
    assertThat(analysis.allLeaks.count()).isEqualTo(1)
    val path = analysis.applicationLeaks.first().leakTraces.first()
    assertThat(path.referencePath.first().referenceName).isEqualTo("ASCII")
  }

  private fun findStringContent(hprofFile: File): HeapAnalysisSuccess {
    val heapAnalyzer = HeapAnalyzer(OnAnalysisProgressListener.NO_OP)
    val analysis = heapAnalyzer.analyze(
      heapDumpFile = hprofFile,
      leakingObjectFinder = { graph ->
        graph.findClassByName("java.lang.String")!!.instances.map { instance ->
          instance["java.lang.String", "value"]?.value?.asNonNullObjectId!!
        }.toSet()
      },
      referenceMatchers = AndroidReferenceMatchers.appDefaults,
      computeRetainedHeapSize = true,
      objectInspectors = AndroidObjectInspectors.appDefaults,
      metadataExtractor = AndroidMetadataExtractor
    )
    println(analysis)
    return analysis as HeapAnalysisSuccess
  }
}