eclipse-jdt / eclipse-jdt/eclipse.jdt.core

RenameRefactoring in a WorkspaceJob can result in wrong JavaModelCache contents

Open
#4,370 11 comments 0 reactions 1 assignee Claimed by @jarthana View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.