StackOverflowException's thrown when calling getMethods on an ScObject

Answered

Hi there,

I'm trying to use the syntheticMemberInjector API to add some functions to an Object.  The functions are created with a Scala Macro.

Given the following code:

@ValidatedRequest case class TestRequest(name: Option[String], email: Option[String])

object TestRequest {
implicit val requireName: RequiredField[TestRequest, String] = ...
implicit val requireEmail: RequiredField[TestRequest, String] = ...
}

My macro will expand it to this:

@ValidatedRequest case class TestRequest(name: Option[String], email: Option[String])

object TestRequest {
implicit val requireName: RequiredField[TestRequest, String] = ...
implicit val requireEmail: RequiredField[TestRequest, String] = ...

implicit val evidenceName: ValidatedRequiredField[TestRequest, String] = ...
implicit val evidenceEmail: ValidatedRequiredField[TestRequest, String] = ...
}

 

I've worked out that I need to inject Strings like the following:

 implicit def evidenceName: ValidatedRequiredField[TestRequest, String] = ???

and I've worked out how to create the required string if given a PsiMethod object representing the original "implicit val require....".  So far, so good!

 

My issue is, every time I call obj.getMethods, intelliJ throws endless StackOverflowExceptions.

My idea64.vmoptions file has a 1MB stack size which *should* be sufficient?

-Xms1024m
-Xmx4096m
-Xss1m
-XX:ReservedCodeCacheSize=240m
-XX:+UseConcMarkSweepGC
-XX:SoftRefLRUPolicyMSPerMB=50
-ea
-Dsun.io.useCanonCaches=false
-Djava.net.preferIPv4Stack=true
-Djdk.http.auth.tunneling.disabledSchemes=""
-XX:+HeapDumpOnOutOfMemoryError
-XX:-OmitStackTraceInFastThrow
-Dawt.useSystemAAFontSettings=lcd
-Dsun.java2d.renderer=sun.java2d.marlin.MarlinRenderingEngine

Here is the code for my injector.

import com.intellij.notification.{Notification, NotificationType, Notifications}
import org.jetbrains.plugins.scala.lang.psi.api.toplevel.typedef.{ScClass, ScObject, ScTypeDefinition}
import org.jetbrains.plugins.scala.lang.psi.impl.toplevel.typedef.SyntheticMembersInjector

import scala.util.control.NonFatal

class Injector extends SyntheticMembersInjector {

private val AnnotationName = "com.sa.macros.ValidatedRequest"

override def injectFunctions(source: ScTypeDefinition): Seq[String] =
source match {
case obj: ScObject => obj.fakeCompanionClassOrCompanionClass match {
case clazz: ScClass if hasAnnotation(clazz, AnnotationName) => addCustomFunctions(obj)
case _ => Seq.empty
}
case _ => Seq.empty
}

private def addCustomFunctions(obj: ScObject): Seq[String] = {
try {
obj.getMethods

/*

This above call to getMethods throws heaps of StackOverflowError's

Like this:
9:41 PM Exception: com.intellij.openapi.progress.ProcessCanceledException: java.lang.StackOverflowError

9:41 PM Exception: com.intellij.openapi.progress.ProcessCanceledException: java.lang.StackOverflowError

9:41 PM Exception: com.intellij.openapi.progress.ProcessCanceledException: java.lang.StackOverflowError

9:41 PM Exception: com.intellij.openapi.progress.ProcessCanceledException: java.lang.StackOverflowError

9:41 PM Exception: com.intellij.openapi.progress.ProcessCanceledException: java.lang.StackOverflowError

*/
} catch {
case NonFatal(e) => notify("Exception", e.toString)
}

Nil
}

private def notify(title: String, message: String): Unit = {
val notification = new Notification("group", title, message, NotificationType.ERROR)

Notifications.Bus.notify(notification)
}

private def hasAnnotation(clazz: ScClass, annotationName: String): Boolean =
Option(clazz.findAnnotation(annotationName)).isDefined

}

 

Can anyone help me work out where to start trying to solve this?  Is it a bug in my code?  Is it a bug in intelliJ?  Is it a bug in the Scala Plugin?

Here is a git repo with instructions for how to reproduce a minimal example.

https://bitbucket.org/matt_gilbert/synthetic-member-injector-issue

 

Cheers

2 comments
Comment actions Permalink

Hi, Matt,

This exception is expected: synthetic injectors are used in `getMethods`, so it makes an infinite recursion. To avoid it you need to use only members that are present in the source code. For example, you may use `obj.extendsBlock.members`.

Another common pitfall is using `hasAnnotation` method, because it actually resolves a reference in the annotation and also may lead to an infinite recursion. Please use `findAnnotationNoAliases` instead.

0
Comment actions Permalink

Hi Nikolay,

Thanks for your quick reply!  I've got it going now.

 

Cheers,

Matt

0

Please sign in to leave a comment.