parse .kt file with anonymous classes

Answered

Hello! I am trying to parse .kt file and get path of classes from root to specific variable.

I parse .kt file like this:

(KtFile) PsiFileFactory.getInstance(
KotlinCoreEnvironment.createForProduction(
Disposer.newDisposable(),
new CompilerConfiguration(),
EnvironmentConfigFiles.JVM_CONFIG_FILES
)
.getProject()
)
.createFileFromText(
source.toString(),
KotlinFileType.INSTANCE,
FileUtil.loadFile(source.toFile())
)

Example:

class TestAnonNames {

  val T0 = "0";

  internal fun f() {
    object : Serializable { // TestAnonNames $1
      val T1 = "T1"
    }
  }
}

 

for variable T0 it's simple, I get KtProperty, then I go to parent's:

KtProperty
KtClassBody
KtClass
KtFile

and KtClass has methods getNameIdentifier/getName - so I could get name of the class

with T1 if I go to parent's I get:

KtProperty
KtClassBody
KtObjectDeclaration
KtObjectLiteralExpression
KtBlockExpression
KtNamedFunction
KtClassBody
KtClass
KtFile

nothing of this has methods like "getName" which returns me "$1". 

how can I get the name of anonymous class?

Thanks! 

19 comments
Comment actions Permalink

and if I do recurse(KtFile):

void recurse(PsiElement element) {
PsiElement[] children = element.getChildren();
for (PsiElement child : children) {
if (child instanceof PsiAnonymousClass) {
System.out.println("anon class");
}
System.out.println(child.getClass());
recurse(child);
}
}

it just print:

class org.jetbrains.kotlin.psi.KtPackageDirective
class org.jetbrains.kotlin.psi.KtImportList
class org.jetbrains.kotlin.psi.KtImportDirective
class org.jetbrains.kotlin.psi.KtDotQualifiedExpression
class org.jetbrains.kotlin.psi.KtDotQualifiedExpression
class org.jetbrains.kotlin.psi.KtNameReferenceExpression
class org.jetbrains.kotlin.psi.KtNameReferenceExpression
class org.jetbrains.kotlin.psi.KtNameReferenceExpression
class com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl
class org.jetbrains.kotlin.psi.KtClass
class org.jetbrains.kotlin.psi.KtClassBody
class org.jetbrains.kotlin.psi.KtNamedFunction
class org.jetbrains.kotlin.psi.KtDeclarationModifierList
class org.jetbrains.kotlin.psi.KtParameterList
class org.jetbrains.kotlin.psi.KtBlockExpression
class org.jetbrains.kotlin.psi.KtObjectLiteralExpression
class org.jetbrains.kotlin.psi.KtObjectDeclaration
class org.jetbrains.kotlin.psi.KtSuperTypeList
class org.jetbrains.kotlin.psi.KtSuperTypeEntry
class org.jetbrains.kotlin.psi.KtTypeReference
class org.jetbrains.kotlin.psi.KtUserType
class org.jetbrains.kotlin.psi.KtNameReferenceExpression
class org.jetbrains.kotlin.psi.KtClassBody
class org.jetbrains.kotlin.psi.KtProperty
class org.jetbrains.kotlin.psi.KtStringTemplateExpression
class org.jetbrains.kotlin.psi.KtLiteralStringTemplateEntry
class com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl

0
Comment actions Permalink

There is no "$1" name in sources. What are you trying to achieve? You can also use PsiViewer plugin to quickly inspect PSI tree and all available properties https://plugins.jetbrains.com/plugin/227-psiviewer

0
Comment actions Permalink

Yes, I know that there's no "$1" in source code.

I want to get the class name where this variable is declared. it's anonymous class, but in bytecode it's $1. can I get it in some way? 

0
Comment actions Permalink

Please try org.jetbrains.kotlin.asJava.toLightClass() for the anonymous class

0
Comment actions Permalink

sorry, maybe I misunderstood you.

But if I find the field T1 in my example, and then go to root of AST, I get classes:

KtProperty
KtClassBody
KtObjectDeclaration
KtObjectLiteralExpression
KtBlockExpression
KtNamedFunction
KtClassBody
KtClass
KtFile

.getLightClass expects KtClassOrObject. in my example there are only:

(KtObjectDeclaration) & (KtClass) instance of (KtClassOrObject).

KtClass is a TestAnonNames class, so I am not interested in it, and KtObjectDeclaration .getLightClass return null (I think because it's about declaration, it's not a class). so I can't get LightClass. 

I called it like:

final Project project = KotlinCoreEnvironment.createForProduction(
Disposer.newDisposable(),
new CompilerConfiguration(),
EnvironmentConfigFiles.JVM_CONFIG_FILES
).getProject();
if (currentParent instanceof KtClassOrObject) {
final KtClassOrObject classOrObject = (KtClassOrObject) currentParent;
final KtLightClass lightClass = KotlinAsJavaSupport.getInstance(project)
.getLightClass(classOrObject);
}

because I can't get extension from lightClassUtils.kt from Java, I suppose.

what can I do? Thanks.

0
Comment actions Permalink

Sorry I meant to pass in the KtObjectDeclaration into toLightClass() == KtCKotlinAsJavaSupport.getInstance(project).getLightClass(KtClass)

0
Comment actions Permalink

sorry, what is KtCKotlinAsJavaSupport? I couldn't find this class in JB repositories. 

I found only CliKotlinAsJavaSupport. but it returns null for KtObjectDeclaration too.

are you sure that I can get the bytecode-name of anonymous class in kotlin?:) I just don't understand even can I do it or not. thanks! 

maybe if you think that it's possible faster to write small code which do it? thanks.  

0
Comment actions Permalink

Sorry, c/p error - I mean you can try to use

KotlinAsJavaSupport.getInstance(project).getLightClass(this)

directly instead of toLightClass() (which just has above line of code)

0
Comment actions Permalink

sorry, I still don't get it.

I wrote a standalone application (but without dependencies), I think it's the better way to show it:

https://paste.ofcode.org/35Eqy7hD6rbhypfhhxDFnau

it prints "null". how can I get the name "$1" ? thanks. 

0
Comment actions Permalink

Anonymous class names usually not specified, so compiler is able to choose whatever it wants. In this particular case this only way is to go through the file and to see anonymous class order in the original file, this will give you a number. Light classes can't help you as they shows only visible from Java class, which is not anonymous in your case.

For what reason do you need this path? Probably we can find different solution rather than hardcoding compiler behaviour.

Best regards,
Alexander Podkhalyuzin.

0
Comment actions Permalink

Yeah, I understand that it is not specified and it's not on Java code, but I thought maybe your parser implements it in someway. 

I analyze bytecode and I have all anonymous classes names, but I want to map them on the original AST from .java, so I need these names. Yeah, I started implement it by myself, but if parser could give me this information it would be awesome. (as I know JB use a lot of things which are not specified:) is it really a problem?) 

0
Comment actions Permalink

It is fairly easy to get the internal anonymous class names.

1. Run analysis over Kotlin files ('KotlinToJVMBytecodeCompiler#analyzeAndGenerate'). You can skip bytecode generation by providing an 'AnalysisHandlerExtension' and returning 'AnalysisResult.success(..., shouldGenerateCode = false)' from it. See 'AbstractKapt3Extension' as an example.
2. Construct a 'GenerationState' – it should be easy since you have all the dependencies. You can do it right inside 'AnalysisHandlerExtension#analysisCompleted'. Use 'GenerationState.Builder' as the primary constructor of 'GenerationState' changes a lot.
3. Call 'generationState.beforeCompile()'.

Now you should be able to get anonymous class names via the 'BindingContext' instance: 'bindingContext[CodegenBinding.ASM_TYPE, classDescriptor]'.

Please bear in mind that our compiler does not have a public API, and everything I described is subject to change.

0
Comment actions Permalink

okay! cool! thanks! I will try this way.

as I understood it doesn't compile your code with "shouldGenerateCode = false"  right? Because I don't really have source code, I use decompiler for it, and often you can't compile code after it. 

 

and I don't write plugin, I want standalone application, can I do it? because

KotlinToJVMBytecodeCompiler.analyzeAndGenerate

requires KotlinCoreEnvironment

0
Comment actions Permalink

> I understood it doesn't compile your code with "shouldGenerateCode = false"  right?

Sure.

> and I don't write plugin, I want standalone application, can I do it?

You will possibly need to provide a component registrar to the compiler configuration. Here is how Kotlinc initializes plugins:

https://github.com/JetBrains/kotlin/blob/4f135a5fe5212b894d5e8c2c5f7b024dd60d639d/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt#L621

By the way, what Kotlin decompiler do you use?

0
Comment actions Permalink

I tried to do what you suggested, and I did almost everything, but I can't figure out some small things. 

My code: https://pastecode.xyz/view/f36f2cf5

1) ClassBuilderFactory for GenerationState.Builder, I use

KotlinLightClassBuilderFactory(PsiJavaFileStubImpl("", false))

(in my example there is no package), is it okay? or do I need something better than this? I am not sure.

2) How can I get ClassDescriptor for context.get(CodegenBinding.ASM_TYPE, classDesc) ? I have only KtFile and didn't find anything how I can get it. 

3) Do I need to work with state inside analysisCompleted or I should use state from analyzeAndGenerate? I have state there and there and I am not sure which one to use, really. or it doesn't matter?

4) Will this solution will be okay with code which doesn't really compile? of some methods have mistakes, for example. Will it be okay to parse "good methods" and skip the rest? or all analyze will fail?

Thanks! 

 

I use procyon as a decompiler:) yes, it's for Java only, but I started with it:) if you know a good decompiler for kotlin - would be good to hear. as I know you don't have it in JB. 

0
Comment actions Permalink

1. You can use `org.jetbrains.kotlin.codegen.ClassBuilderFactories#BINARIES`, this should be enough.

2. You can find a KtClass for which you need to find a name by traversing the AST (PSI tree). Then you can call 'bindingContext[BindingContext.CLASS, ktClass]' and get a 'ClassDescriptor' instance.

3. Both is ok.

4. It should be ok, although 'beforeCompile()' may throw an exception.

We don't have a Kotlin decompiler, that's why I asked you this question. :-)

0
Comment actions Permalink

Sorry, I think I did everything what you said, but I have a small problem anyway. I tried to debug bindings but still didn't figure it out.

Can you look at my code, please?

https://paste.ofcode.org/x4SWjCr4tZF62Kfvd6Kapp

it prints:

sources: [KtFile: TestAnonNames.kt]
analysisCompleted files: [KtFile: TestAnonNames.kt]
analysisCompleted version: 50
analysisCompleted: classDescriptor null
analysisCompleted: type null
main state null

I am not sure why I can't get ClassDescription. and in main I can't get state (earlier you said that it should work both ways). Thanks!

0
Comment actions Permalink

ping, sorry?

0
Comment actions Permalink

Sorry for the delay.

I suppose you missed to register some of IntelliJ services, without those analyzer won't work properly. Unfortunately, it's not a simple thing to do. You can use Android Lint CLI as an example:

https://android.googlesource.com/platform/tools/base/+/refs/heads/mirror-goog-studio-master-dev/lint/cli/src/main/java/com/android/tools/lint/LintCoreApplicationEnvironment.java

0

Please sign in to leave a comment.