override def evaluate()

in scala/debugger/src/org/jetbrains/plugins/scala/debugger/evaluation/evaluator/ScalaMethodEvaluator.scala [40:285]


  override def evaluate(context: EvaluationContextImpl): AnyRef = {
    val debugProcess: DebugProcessImpl = context.getDebugProcess
    if (!debugProcess.isAttached) return null
    if (debugProcess != prevProcess) {
      initCache(debugProcess)
    }
    val requiresSuperObject: Boolean = objectEvaluator.isInstanceOf[ScSuperEvaluator] ||
      (objectEvaluator.is[DisableGC] &&
        DisableGC.unwrap(objectEvaluator).isInstanceOf[ScSuperEvaluator])
    val evaluated: AnyRef = {
      val res = objectEvaluator.evaluate(context)
      if (methodOnRuntimeRef) res else DebuggerUtil.unwrapScalaRuntimeRef(res)
    }
    val obj = evaluated match {
      case p: PrimitiveValue => ScalaBoxingEvaluator.box(p, context)
      case _ => evaluated
    }
    if (obj == null) {
      throw EvaluationException(new NullPointerException)
    }
    val args: Seq[Value] = argumentEvaluators.flatMap { ev =>
      ev.evaluate(context) match {
        case Some(res) => Some(res.asInstanceOf[Value])
        case None => None
        case res => Some(res.asInstanceOf[Value])
      }
    }
    try {
      def findClass(name: String): ReferenceType = debugProcess.findClass(context, name, context.getClassLoader)

      def findMethod(referenceType: ReferenceType): Option[Method] = {
        lazy val sortedMethodCandidates: List[Method] = {
          val allMethods = referenceType.allMethods()
          allMethods.asScala.collect {
            case method if !localMethod && method.name() == methodName => (method, 1)
            case method if !localMethod && method.name().endsWith("$$" + methodName) => (method, 1) //private method, maybe from parent class
            case method if localMethod && method.name() == localMethodName => (method, 1)
            case method if localMethod && method.name.startsWith(methodName + "$") => (method, 2)
            case method if localMethod && method.name.contains(methodName + "$") => (method, 3)
          }
            .sortBy(_._2)
            .map(_._1)
            .toList
        }
        def concreteMethodByName(mName: String, signature: JVMName): Option[Method] = {
          val sgn = signature.getName(debugProcess)
          referenceType match {
            case classType: ClassType =>
              Option(classType.concreteMethodByName(mName, sgn))
            case it: InterfaceType =>
              // This is a dangerous way of querying if a method is concrete (i.e. has an implementation).
              // The `ConcreteMethodImpl` class does not exist in all JDK distributions, particularly modern ones
              // like JDK 21. In fact, the Amazon Corretto JDK distribution does not have this class.
              // Luckily, this code runs in the JBR, so this class exist, but we still are not allowed to link
              // against it during compilation.
              // TODO: Rewrite using proper APIs.
              Try {
                val concreteMethodImplClass = Class.forName("com.sun.tools.jdi.ConcreteMethodImpl")
                it.methodsByName(mName, sgn).asScala.find(concreteMethodImplClass.isInstance)
              }.toOption.flatten
          }
        }
        def findWithSignature(): Option[Method] = {
          if (signature == null) None
          else {
            if (!localMethod) concreteMethodByName(methodName, signature)
            else {
              sortedMethodCandidates.to(LazyList)
                .flatMap(m => concreteMethodByName(m.name(), signature))
                .headOption
            }
          }
        }
        def findWithoutSignature(): Option[Method] = {
          def sameParamNumber(m: Method) = {
            try {
              val argsCount = m.argumentTypeNames().size()
              if (m.isVarArgs) args.length >= argsCount
              else args.length == argsCount || args.length == argsCount - 1
            }
            catch {
              case _: AbsentInformationException => true
            }
          }
          def linesIntersects(m: Method): Boolean = inReadAction {
            Try {
              val lines = methodPosition.map(_.getLine)
              m.allLineLocations().asScala.exists(l => lines.contains(ScalaPositionManager.checkedLineNumber(l)))
            }.getOrElse(true)
          }

          if (sortedMethodCandidates.length > 1) {
            val withSameParamNumber = sortedMethodCandidates.filter(sameParamNumber)
            if (withSameParamNumber.isEmpty) sortedMethodCandidates.headOption
            else if (withSameParamNumber.length == 1) withSameParamNumber.headOption
            else {
              val withSameLines = withSameParamNumber.filter(linesIntersects)
              withSameLines.headOption.orElse(withSameParamNumber.headOption)
            }
          }
          else sortedMethodCandidates.headOption
        }

        def doFind() = findWithSignature() orElse findWithoutSignature()

        jdiMethodsCache.getOrElseUpdate(referenceType, doFind())
      }

      def invokeStaticMethod(jdiMethod: Method): AnyRef = {
        def fixArguments(): Seq[Value] = {
          def correctArgType(arg: AnyRef, typeName: String) = arg match {
            case objRef: ObjectReference => DebuggerUtils.instanceOf(objRef.referenceType(), typeName)
            case primValue: PrimitiveValue => primValue.`type`().name() == typeName
            case _ => true
          }
          val paramTypeNames = jdiMethod.argumentTypeNames()
          if (paramTypeNames.size() == 0) Seq.empty
          else {
            val needObj = args.isEmpty || !correctArgType(args.head, paramTypeNames.get(0))
            if (needObj) unwrappedArgs(obj +: args, jdiMethod)
            else unwrappedArgs(args, jdiMethod)
          }
        }

        jdiMethod.declaringType() match {
          case ct: ClassType =>
            debugProcess.invokeMethod(context, ct, jdiMethod, fixArguments().asJava)
          case it: InterfaceType =>
            debugProcess.invokeMethod(context, it, jdiMethod, fixArguments().asJava)
        }
      }

      def invokeConstructor(referenceType: ReferenceType, method: Method): AnyRef = {
        referenceType match {
          case ct: ClassType if methodName == "<init>" =>
            debugProcess.newInstance(context, ct, method, unwrappedArgs(args, method).asJava)
          case _ => throw EvaluationException(DebuggerBundle.message("could.not.find.appropriate.constructor.for.name", referenceType.name()))
        }
      }

      def invokeInstanceMethod(objRef: ObjectReference, jdiMethod: Method): AnyRef = {
        if (requiresSuperObject)
          debugProcess.invokeInstanceMethod(context, objRef, jdiMethod, unwrappedArgs(args, jdiMethod).asJava, ObjectReference.INVOKE_NONVIRTUAL)
        else
          debugProcess.invokeMethod(context, objRef, jdiMethod, unwrappedArgs(args, jdiMethod).asJava)
      }

      def invokeInterfaceMethod(objRef: ObjectReference, jdiMethod: Method): AnyRef = {
        def togglePrivate(method: Method): Unit = {
          try {
            // This is a dangerous way of making a private trait method callable.
            // The `TypeComponentImpl` class does not exist in all JDK distributions, particularly modern ones
            // like JDK 21. In fact, the Amazon Corretto JDK distribution does not have this class.
            // Luckily, this code runs in the JBR, so this class exist, but we still are not allowed to link
            // against it during compilation.
            // TODO: Rewrite using proper APIs.
            val typeComponentImplClass = Class.forName("com.sun.tools.jdi.TypeComponentImpl")
            method match {
              case mImpl if typeComponentImplClass.isInstance(mImpl) =>
                val field = typeComponentImplClass.getDeclaredField("modifiers")
                field.setAccessible(true)
                val value = field.get(mImpl).asInstanceOf[Integer].toInt
                val privateModifierMask = 2
                field.set(mImpl, value ^ privateModifierMask)
              case _ =>
            }
          } catch {
            case _: Throwable =>
          }
        }

        if (jdiMethod.isAbstract) throw EvaluationException(DebuggerBundle.message("cannot.invoke.abstract.interface.method.name", jdiMethod.name()))

        //see SCL-10132
        if (!jdiMethod.isDefault && jdiMethod.isPrivate) {
          togglePrivate(jdiMethod)
          val result = debugProcess.invokeInstanceMethod(context, objRef, jdiMethod, unwrappedArgs(args, jdiMethod).asJava, ObjectReference.INVOKE_NONVIRTUAL)
          togglePrivate(jdiMethod)
          result
        } else {
          debugProcess.invokeMethod(context, objRef, jdiMethod, unwrappedArgs(args, jdiMethod).asJava)
        }
      }

      def classWithMethod(c: ReferenceType) = findMethod(c).map(m => (c, m))

      def findInSuperClass(classType: ClassType): Option[(ReferenceType, Method)] = {
        val superClass = classType.superclass()
        classWithMethod(superClass)
          .orElse {
            traitImplementation.flatMap(ti => Option(ti.getName(context.getDebugProcess))) match {
              case Some(traitImplName) =>
                Try(findClass(traitImplName)) match {
                  case Success(c: ClassType) => classWithMethod(c)
                  case _ =>
                    val traitName = traitImplName.stripSuffix(TraitImplementationClassSuffix_211)
                    Try(findClass(traitName)).toOption.flatMap(classWithMethod)
                }
              case _ => None
            }
          }
      }

      val typeAndMethod: Option[(ReferenceType, Method)] = obj match {
        case objRef: ObjectReference =>
          val objType = objRef.referenceType()
          if (objType.is[ArrayType]) {
            Option(signature).map(_.getName(debugProcess)).flatMap { sgn =>
              Option(DebuggerUtils.findMethod(objType, methodName, sgn))
            }.fold {
              throw EvaluationException(DebuggerBundle.message("method.methodname.cannot.be.invoked.on.array", methodName))
            }(Some(objType, _))
          } else {
            val classType = objType.asInstanceOf[ClassType]
            if (requiresSuperObject) findInSuperClass(classType)
            else classWithMethod(classType)
          }
        case rt: ReferenceType =>
          classWithMethod(rt)
        case _ =>
          throw EvaluationException(JavaDebuggerBundle.message("evaluation.error.evaluating.method", methodName))
      }

      if (typeAndMethod.isEmpty) throw EvaluationException(JavaDebuggerBundle.message("evaluation.error.evaluating.method", methodName))

      typeAndMethod match {
        case Some((tp, m)) if m.isConstructor =>
          invokeConstructor(tp, m)
        case Some((_, m)) if m.isStatic =>
          invokeStaticMethod(m)
        case Some((_, m)) =>
          obj match {
            case objRef: ObjectReference if m.declaringType().is[InterfaceType] =>
              invokeInterfaceMethod(objRef, m)
            case objRef: ObjectReference =>
              invokeInstanceMethod(objRef, m)
            case _ =>
              throw EvaluationException(JavaDebuggerBundle.message("evaluation.error.evaluating.method", methodName))
          }
        case _ => throw EvaluationException(JavaDebuggerBundle.message("evaluation.error.evaluating.method", methodName))
      }
    }
    catch {
      case e: Exception => throw EvaluationException(JavaDebuggerBundle.message("evaluation.error.evaluating.method", methodName), e)
    }
  }