1

In our previous project, we have a class called AbstractActor to be inherited to do specific stuff, and some of our devs might hold its reference as a constructed valOrVar parameter or KTProperty, which eventually had led to a garbage collection disaster, the actor could not be collected since it was referenced and the memory usage went rocket high. So my leader asks me to create a new rule to prevent this, I have tried many ways yet I just couldn't get the KClass to determine if it is isAssignableFrom or isSubclassOf to the AbstractActor class. Here is the test example I created

class ActorRefTest {

  @Test
  fun noExplicitlyReferencingActorRule() {
    val env = createEnvironment()
    val findings = NoExplicitlyReferencingActorRule(Config.empty).compileAndLintWithContext(
      env.env, """
        package testRule
open class TypedActor : AbstractActor() {
  override fun receive(msg: Any) {
  
  }


}
class Typed1Actor : TypedActor() {
  


}

abstract class AbstractActor {
  abstract fun receive(msg: Any) 
}
//@Suppress("NoExplicitlyReferencingActorRule")
class ExplicitlyReferencingActor(val actor: Typed1Actor, val isEnabled: Boolean) {
//       lateinit var actor2: AbstractActor 
        val isEnabled : Boolean = false

}

        """.trimIndent()
    )
    findings.forEach {
      println(it)
    }
    assert(findings.isNotEmpty())
  }

}

And this is the rule I created

package testRuleSet

import io.gitlab.arturbosch.detekt.api.*
import io.gitlab.arturbosch.detekt.rules.identifierName
import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.typeBinding.createTypeBindingForReturnType

class NoExplicitlyReferencingActorRule(config: Config) : Rule(config) {
  override val issue: Issue = Issue(
    javaClass.simpleName,
    Severity.Style,
    "Customized-NoExplicitlyReferencingActor",
    Debt.TEN_MINS,
  )

  override fun visitParameter(parameter: KtParameter) {
    super.visitParameter(parameter)
    if (parameter.hasValOrVar()) {
      if (validateDeclaration(parameter)) {
        reportCodeSmell(parameter)
      }
    }
  }

  private fun isSubClassOfAbsractActor(parameter: KtCallableDeclaration): Boolean {
    return parameter.createTypeBindingForReturnType(bindingContext)?.type?.constructor?.supertypes?.find {
      it.getJetTypeFqName(
        false
      ) == "testRule.AbstractActor"
    } != null
  }

  private fun validateDeclaration(declaration: KtCallableDeclaration): Boolean {
    if (bindingContext == BindingContext.EMPTY) {
      return false
    }
    return isSubClassOfAbsractActor(declaration)
  }

  override fun visitProperty(property: KtProperty) {
    super.visitProperty(property)
    if (property.isMember) {
      if (validateDeclaration(property)) {
        reportCodeSmell(property)
      }
    }
  }

  private fun reportCodeSmell(declaration: KtCallableDeclaration) {
    report(
      CodeSmell(
        issue,
        Entity.from(requireNotNull(declaration.colon)),
        "do not hold any subclass of AbsractActor as a explicit reference"
      )
    )
  }


}

From what I see in the stacktrace, detekt and compiler know the exact package and identifier name but I just couldn't get it right, what am I missing here?

0 Answers0