[groovy] properly reset classloader caches after stub generation is finished (IDEA-322782)

If class loaders are used to resolve references to classes in groovyc, it may happen that class file for groovy file is compiled from generated stub after groovy stub generation finished. To avoid "Unable to load class" error, we need to clear caches of the class loader which was created before stub generation started. Also, we need to disable the usage of classpath.index files which are updated only when the module chunk is fully compiled.

Second compilation is still needed in such cases, but given that it's performed automatically, it doesn't affect users much.

GitOrigin-RevId: 53b265c8966df06900e6b05558ce01ea8dfa77e9
This commit is contained in:
Nikolay Chashnikov
2023-12-04 17:10:41 +01:00
committed by intellij-monorepo-bot
parent b906475501
commit c2ff557fc2
2 changed files with 47 additions and 2 deletions

View File

@@ -108,7 +108,7 @@ final class InProcessGroovyc implements GroovycFlavor {
//noinspection unchecked
Queue<String> toGroovyc = (Queue<String>)msg;
loader.resetCache();
return createContinuation(future, toGroovyc, parser);
return createContinuation(future, toGroovyc, parser, loader);
}
else if (msg != null) {
throw new AssertionError("Unknown message: " + msg);
@@ -119,11 +119,13 @@ final class InProcessGroovyc implements GroovycFlavor {
@NotNull
private static GroovycContinuation createContinuation(Future<Void> future,
@NotNull Queue<String> mailbox,
GroovycOutputParser parser) {
GroovycOutputParser parser,
@NotNull JointCompilationClassLoader loader) {
return new GroovycContinuation() {
@NotNull
@Override
public GroovyCompilerResult continueCompilation() throws Exception {
loader.resetCache();
parser.onContinuation();
mailbox.offer(GroovyRtConstants.JAVAC_COMPLETED);
future.get();
@@ -206,6 +208,10 @@ final class InProcessGroovyc implements GroovycFlavor {
return UrlClassLoader.build().
files(toPaths(compilationClassPath))
.parent(parent)
/* obsolete classpath.index files are deleted only after compilation of the module chunk finishes, so they may not include *.class
files produced by javac during compilation of this chunk;
therefore, persistent index should be disabled for Groovy class loader */
.usePersistentClasspathIndexForLocalClassDirectories(false)
.useCache(ourLoaderCachePool, file -> {
String filePath = FileUtil.toCanonicalPath(file.toString());
for (String output : myOutputs) {

View File

@@ -1,11 +1,14 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.plugins.groovy.compiler
import com.intellij.compiler.CompilerConfiguration
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.module.Module
import com.intellij.openapi.util.io.FileUtil
import groovy.transform.CompileStatic
import org.jetbrains.groovy.compiler.rt.GroovyRtConstants
import org.jetbrains.jps.incremental.groovy.JpsGroovycRunner
import org.jetbrains.plugins.groovy.TestLibrary
import static com.intellij.testFramework.EdtTestUtil.runInEdtAndWait
@@ -56,6 +59,42 @@ class Bar {}'''
assert msg.message.contains('org.apache.commons.logging.Log')
}
void "test circular dependency with in-process class loading resolving"() {
def groovyFile = myFixture.addFileToProject('mix/GroovyClass.groovy', '''
package mix
@groovy.transform.CompileStatic
class GroovyClass {
JavaClass javaClass
String bar() {
return javaClass.foo()
}
}
''')
myFixture.addFileToProject('mix/JavaClass.java', '''
package mix;
public class JavaClass {
GroovyClass groovyClass;
public String foo() {
return "foo";
}
}
''')
CompilerConfiguration.getInstance(project).buildProcessVMOptions +=
" -D$JpsGroovycRunner.GROOVYC_IN_PROCESS=true -D$GroovyRtConstants.GROOVYC_ASM_RESOLVING_ONLY=false"
assertEmpty(make())
touch(groovyFile.virtualFile)
def messages = make()
/* since only groovy file is changed, its class file is deleted, but javac isn't called (JavaBuilder.compile returns early), so
GroovyClass.class file from the generated stub isn't produced, and the classloader failed to load JavaClass during compilation of
GroovyClass. After chunk rebuild is requested, javac is called so it compiles the stub and groovyc finishes successfully.
*/
assert messages.collect { it.message } == chunkRebuildMessage("Groovy compiler")
}
protected List<String> chunkRebuildMessage(String builder) {
return ['Builder "' + builder + '" requested rebuild of module chunk "mainModule"']
}