Repository: square/leakcanary
File: shark-android/src/main/java/shark/AndroidObjectInspectors.kt (Kotlin)

Code before:
 * will not be influenced by those mistakes.
 *
 * Most developers should use the entire set of default [ObjectInspector] by calling [appDefaults],
 * unless there's a bug and you temporarily want to remove an inspector.
 */
enum class AndroidObjectInspectors : ObjectInspector {

  VIEW {
    override val leakingObjectFilter = { heapObject: HeapObject ->
      if (heapObject is HeapInstance && heapObject instanceOf "android.view.View") {
        val mContext =
          heapObject.getOrThrow("android.view.View", "mContext").value.asObject!!.asInstance!!
        val activityContext = mContext.unwrapActivityContext()
        (activityContext != null &&
            activityContext["android.app.Activity", "mDestroyed"]?.value?.asBoolean == true)
      } else false
    }

    override fun inspect(
      reporter: ObjectReporter
    ) {
      reporter.whenInstanceOf("android.view.View") { instance ->
        // This skips edge cases like Toast$TN.mNextView holding on to an unattached and unparented
        // next toast view
        val mParentRef =
          instance.getOrThrow("android.view.View", "mParent").value
        val mParentSet = mParentRef.isNonNullReference
        val mWindowAttachCount =
          instance.getOrThrow("android.view.View", "mWindowAttachCount").value.asInt!!
        val viewDetached =
          instance.getOrThrow("android.view.View", "mAttachInfo").value.isNullReference
        val mContext =
          instance.getOrThrow("android.view.View", "mContext").value.asObject!!.asInstance!!

        val activityContext = mContext.unwrapActivityContext()
        labels += if (activityContext == null) {
          "mContext instance of ${mContext.instanceClassName}, not wrapping activity"
        } else {
          val activityDescription =
            "with mDestroyed = " + (activityContext["android.app.Activity", "mDestroyed"]?.value?.asBoolean?.toString()
                ?: "UNKNOWN")
          if (activityContext == mContext) {
            "mContext instance of ${activityContext.instanceClassName} $activityDescription"
          } else {
            "mContext instance of ${mContext.instanceClassName}, wrapping activity ${activityContext.instanceClassName} $activityDescription"
          }
        }
        if (activityContext != null && activityContext["android.app.Activity", "mDestroyed"]?.value?.asBoolean == true) {
          leakingReasons += "View.mContext references a destroyed activity"
        } else {
          if (mParentSet && mWindowAttachCount > 0) {

Review comment (suggestion):
Obfuscation shouldn't impact classes from the Android SDK. This isn't a bad idea but should likely be a different method that throws a different exception (which states that it looks like AOSP might have changed)

Code after:
 * will not be influenced by those mistakes.
 *
 * Most developers should use the entire set of default [ObjectInspector] by calling [appDefaults],
 * unless there's a bug and you temporarily want to remove an inspector.
 */
enum class AndroidObjectInspectors : ObjectInspector {

  VIEW {
    override val leakingObjectFilter = { heapObject: HeapObject ->
      if (heapObject is HeapInstance && heapObject instanceOf "android.view.View") {
        val mContext = heapObject["android.view.View", "mContext"]!!.value.asObject!!.asInstance!!
        val activityContext = mContext.unwrapActivityContext()
        (activityContext != null &&
            activityContext["android.app.Activity", "mDestroyed"]?.value?.asBoolean == true)
      } else false
    }

    override fun inspect(
      reporter: ObjectReporter
    ) {
      reporter.whenInstanceOf("android.view.View") { instance ->
        // This skips edge cases like Toast$TN.mNextView holding on to an unattached and unparented
        // next toast view
        val mParentRef = instance["android.view.View", "mParent"]!!.value
        val mParentSet = mParentRef.isNonNullReference
        val mWindowAttachCount =
          instance["android.view.View", "mWindowAttachCount"]?.value!!.asInt!!
        val viewDetached = instance["android.view.View", "mAttachInfo"]!!.value.isNullReference
        val mContext = instance["android.view.View", "mContext"]!!.value.asObject!!.asInstance!!

        val activityContext = mContext.unwrapActivityContext()
        labels += if (activityContext == null) {
          "mContext instance of ${mContext.instanceClassName}, not wrapping activity"
        } else {
          val activityDescription =
            "with mDestroyed = " + (activityContext["android.app.Activity", "mDestroyed"]?.value?.asBoolean?.toString()
                ?: "UNKNOWN")
          if (activityContext == mContext) {
            "mContext instance of ${activityContext.instanceClassName} $activityDescription"
          } else {
            "mContext instance of ${mContext.instanceClassName}, wrapping activity ${activityContext.instanceClassName} $activityDescription"
          }
        }
        if (activityContext != null && activityContext["android.app.Activity", "mDestroyed"]?.value?.asBoolean == true) {
          leakingReasons += "View.mContext references a destroyed activity"
        } else {
          if (mParentSet && mWindowAttachCount > 0) {
            if (viewDetached) {
              leakingReasons += "View detached and has parent"
            } else {
              val viewParent = mParentRef.asObject!!.asInstance!!