eclipse-jdt / eclipse-jdt/eclipse.jdt.core
RenameRefactoring in a WorkspaceJob can result in wrong JavaModelCache contents
- Dominant language
- Java
- Stars
- 237
- Forks
- 195
- Avg merge
- 1d 12h
- Merged PRs (30d)
- 47
Description
We observe a problem in our tests, where a refactor rename we do in a workspace job results in invalid JDT model cache contents.
Code that tries to access the primary type of a compilation unit fails - no info is found for the class of the CU based on the CU source file name. The info that is in the model cache is outdated, it contains the old class name (before the refactor rename).
By the looks of it, the following code runs in parallel to the refactor rename saving code modifications:
```
at org.eclipse.jdt.internal.core.JavaModelCache.removeInfo(JavaModelCache.java:282)
at org.eclipse.jdt.internal.core.JavaModelManager.removeInfoAndChildren(JavaModelManager.java:4260)
at org.eclipse.jdt.internal.core.JavaElement.close(JavaElement.java:144)
at org.eclipse.jdt.internal.core.CompilationUnit.close(CompilationUnit.java:332)
at org.eclipse.jdt.internal.core.DeltaProcessor.close(DeltaProcessor.java:682)
at org.eclipse.jdt.internal.core.DeltaProcessor.elementAdded(DeltaProcessor.java:1233)
at org.eclipse.jdt.internal.core.DeltaProcessor.updateCurrentDeltaAndIndex(DeltaProcessor.java:2586)
at org.eclipse.jdt.internal.core.DeltaProcessor.traverseDelta(DeltaProcessor.java:2318)
at org.eclipse.jdt.internal.core.DeltaProcessor.traverseDelta(DeltaProcessor.java:2368)
at org.eclipse.jdt.internal.core.DeltaProcessor.traverseDelta(DeltaProcessor.java:2368)
at org.eclipse.jdt.internal.core.DeltaProcessor.traverseDelta(DeltaProcessor.java:2368)
at org.eclipse.jdt.internal.core.DeltaProcessor.processResourceDelta(DeltaProcessor.java:1974)
at org.eclipse.jdt.internal.core.DeltaProcessor.resourceChanged(DeltaProcessor.java:2145)
at org.eclipse.jdt.internal.core.DeltaProcessingState.resourceChanged(DeltaProcessingState.java:490)
at org.eclipse.core.internal.events.NotificationManager$1.run(NotificationManager.java:335)
at org.eclipse.core.runtime.SafeRunner.run(SafeRunner.java:47)
at org.eclipse.core.internal.events.NotificationManager.notify(NotificationManager.java:324)
at org.eclipse.core.internal.events.NotificationManager.broadcastChanges(NotificationManager.java:178)
at org.eclipse.core.internal.resources.Workspace.broadcastPostChange(Workspace.java:473)
at org.eclipse.core.internal.resources.Workspace.endOperation(Workspace.java:1619)
at org.eclipse.core.internal.resources.InternalWorkspaceJob.run(InternalWorkspaceJob.java:51)
at org.eclipse.core.internal.jobs.Worker.run(Worker.java:63)
```
Then the refactor rename save does odd things, saving the wrong info for the class in the JDT model cache.
To reproduce:
In JDT UI, add the following test:
```
diff --git a/org.eclipse.jdt.ui.tests.refactoring/test cases/org/eclipse/jdt/ui/tests/refactoring/RenameTypeTests.java b/org.eclipse.jdt.ui.tests.refactoring/test cases/org/eclipse/jdt/ui/tests/refactoring/RenameTypeTests.java
index df2c40f827..1b43fbc7f2 100644
--- a/org.eclipse.jdt.ui.tests.refactoring/test cases/org/eclipse/jdt/ui/tests/refactoring/RenameTypeTests.java
+++ b/org.eclipse.jdt.ui.tests.refactoring/test cases/org/eclipse/jdt/ui/tests/refactoring/RenameTypeTests.java
@@ -19,6 +19,7 @@ import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@@ -31,12 +32,17 @@ import org.junit.Test;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.Status;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.WorkspaceJob;
+import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.Refactoring;
import org.eclipse.ltk.core.refactoring.RefactoringDescriptor;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
@@ -58,7 +64,10 @@ import org.eclipse.jdt.core.refactoring.IJavaElementMapper;
import org.eclipse.jdt.core.refactoring.IJavaRefactorings;
import org.eclipse.jdt.core.refactoring.descriptors.RenameJavaElementDescriptor;
+import org.eclipse.jdt.internal.compiler.env.IElementInfo;
+import org.eclipse.jdt.internal.core.JavaModelManager;
import org.eclipse.jdt.internal.core.refactoring.descriptors.RefactoringSignatureDescriptorFactory;
+import org.eclipse.jdt.internal.corext.refactoring.rename.RenameCompilationUnitProcessor;
import org.eclipse.jdt.internal.corext.refactoring.rename.RenameTypeProcessor;
import org.eclipse.jdt.internal.corext.refactoring.rename.RenamingNameSuggestor;
import org.eclipse.jdt.internal.corext.refactoring.tagging.INameUpdating;
@@ -1926,4 +1935,51 @@ public class RenameTypeTests extends GenericRefactoringTest {
// Test references in annotations and type parameters
helper3("Try", "Bla", true, false, true);
}
+
+ @Test
+ public void testRenameProcessor() throws Exception {
+ for (int idx = 0; idx < 1; ++idx) {
+ final int i = idx;
+ WorkspaceJob j = new WorkspaceJob("test job " + i) {
+ @Override
+ public IStatus runInWorkspace(IProgressMonitor pm) throws CoreException {
+ ICompilationUnit compilationUnit = null;
+ try {
+ compilationUnit = createCU(getPackageP(), "X" + i + ".java", "package p; class X" + i + "{}");
+ RenameCompilationUnitProcessor renameProcessor = new RenameCompilationUnitProcessor(compilationUnit);
+ renameProcessor.setNewElementName("X" + i + "New");
+ RenameRefactoring renameRefactoring = new RenameRefactoring(renameProcessor);
+ renameRefactoring.setValidationContext(null); // no UI context
+ RefactoringStatus checkStatus = renameRefactoring.checkAllConditions(pm);
+ assertFalse("Status has errors: " + checkStatus.getMessageMatchingSeverity(RefactoringStatus.FATAL), checkStatus.hasFatalError());
+ Change renameChange = renameRefactoring.createChange(pm);
+ renameChange.perform(pm);
+ compilationUnit= getPackageP().getCompilationUnit("X" + i + "New.java");
+ String s = compilationUnit.getSource();
+ IElementInfo info= JavaModelManager.getJavaModelManager().getInfo(compilationUnit);
+ String f = info.toString();
+ System.out.println("info:" + f);
+ System.out.println("source:" + s);
+ assertTrue("Invalid info: " + f, f.contains("class X" + i + "New [in X" + i + "New.java"));
+ assertEquals("Invalid source: " + s, "package p; class X" + i + "New{}", s);
+ } catch (Throwable e) {
+ IStatus error = Status.error("test job error", e);
+ throw new CoreException(error);
+ } finally {
+ if (compilationUnit != null) {
+ compilationUnit.delete(true, pm);
+ }
+ }
+ return Status.OK_STATUS;
+ }
+ };
+ j.schedule();
+ j.join(30_000, new NullProgressMonitor());
+ IStatus r= j.getResult();
+ if (!r.isOK()) {
+ fail("exception: " + r.getMessage(), r.getException());
+ }
+ JavaModelManager.getIndexManager().waitForIndex(true, new NullProgressMonitor());
+ }
+ }
}
```
In JDT core, add the following:
```
diff --git a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavaModelCache.java b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavaModelCache.java
index d9ec10ed67..37b48952f3 100644
--- a/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavaModelCache.java
+++ b/org.eclipse.jdt.core/model/org/eclipse/jdt/internal/core/JavaModelCache.java
@@ -215,6 +215,9 @@ protected IElementInfo peekAtInfo(IJavaElement element) {
* Remember the info for the element.
*/
protected void putInfo(IJavaElement element, IElementInfo info) {
+ if (element.getElementType() == IJavaElement.COMPILATION_UNIT && element.getElementName().startsWith("X") && element.getElementName().endsWith("New.java")) {
+ System.out.println("Putting info");
+ }
switch (element.getElementType()) {
case IJavaElement.JAVA_MODEL:
this.modelInfo = (JavaElementInfo) info;
```
Set a breakpoint at the new `System.out`, debug the JDT UI test as a plug-in test, wait a moment at each breakpoint hit before resuming. Observe the fail:
```
org.opentest4j.AssertionFailedError: exception: test job error
at org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:42)
at org.junit.jupiter.api.Assertions.fail(Assertions.java:150)
at org.eclipse.jdt.ui.tests.refactoring.RenameTypeTests.testRenameProcessor(RenameTypeTests.java:1980)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
Caused by: java.lang.AssertionError: Invalid info: CompilationUnitElementInfo [isStructureKnown=true, children=[package p [in X0New.java [in p [in src [in TestProject1756835844245]]]], class X0 [in X0New.java [in p [in src [in TestProject1756835844245]]]]], ]
at org.eclipse.jdt.ui.tests.refactoring.RenameTypeTests$1.runInWorkspace(RenameTypeTests.java:1963)
at org.eclipse.core.internal.resources.InternalWorkspaceJob.run(InternalWorkspaceJob.java:43)
at org.eclipse.core.internal.jobs.Worker.run(Worker.java:63)
```
See also screen recording:
https://github.com/user-attachments/assets/01897011-e916-4b2e-b65b-2129f3ffce8b
Reproduced with latest JDT core and UI, running on:
```
Eclipse SDK
Version: 2025-12 (4.38)
Build id: I20250901-0300
```
Contributor guide
Assessment
This issue has not been assessed yet.