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