Bug 573479 - skip already visited entries when expanding classpath

DefaultProjectClasspathEntry.expandProject() performs a depth first
iteration over the projects classpath. It tries to prune already visited
sub trees by checking if the entry is already in the expandedPath list.
One code path however transforms the entry before adding it to the list.
In that case the pruning does not work.
Fixed by introducing a dedicated set to keep track of already visited
entries.

Change-Id: I0c0946b597e225645501a410a9f14614fa6cae62
Signed-off-by: Andreas Huber <ahe@lucares.de>
Reviewed-on: https://git.eclipse.org/r/c/jdt/eclipse.jdt.debug/+/180466
Tested-by: JDT Bot <jdt-bot@eclipse.org>
Reviewed-by: Andrey Loskutov <loskutov@gmx.de>
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/DefaultProjectClasspathEntry.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/DefaultProjectClasspathEntry.java
index d2b45ab..8002e80 100644
--- a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/DefaultProjectClasspathEntry.java
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/DefaultProjectClasspathEntry.java
@@ -15,8 +15,10 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
 
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.resources.IResource;
@@ -199,6 +201,11 @@
 	 *                if unable to expand the classpath
 	 */
 	public static void expandProject(IClasspathEntry projectEntry, List<Object> expandedPath, List<IClasspathEntry> expanding, boolean excludeTestCode, boolean exportedEntriesOnly, IJavaProject rootProject, boolean isModularJVM) throws CoreException {
+		final Set<Object> visitedEntries = new HashSet<>();
+		expandProjectInternal(projectEntry, expandedPath, visitedEntries, expanding, excludeTestCode, exportedEntriesOnly, rootProject, isModularJVM);
+	}
+
+	public static void expandProjectInternal(IClasspathEntry projectEntry, List<Object> expandedPath, Set<Object> visitedEntries, List<IClasspathEntry> expanding, boolean excludeTestCode, boolean exportedEntriesOnly, IJavaProject rootProject, boolean isModularJVM) throws CoreException {
 		expanding.add(projectEntry);
 		// 1. Get the raw classpath
 		// 2. Replace source folder entries with a project entry
@@ -250,7 +257,7 @@
 				switch (entry.getEntryKind()) {
 					case IClasspathEntry.CPE_PROJECT:
 						if (!expanding.contains(entry)) {
-							expandProject(entry, expandedPath, expanding, excludeTestCode, exportedEntriesOnly, rootProject, isModularJVM);
+							expandProjectInternal(entry, expandedPath, visitedEntries, expanding, excludeTestCode, exportedEntriesOnly, rootProject, isModularJVM);
 						}
 						break;
 					case IClasspathEntry.CPE_CONTAINER:
@@ -338,17 +345,20 @@
 						if (!expandedPath.contains(entry)) {
 							// resolve project relative paths - @see bug 57732 & bug 248466
 							if (entry.getEntryKind() != IClasspathEntry.CPE_SOURCE) {
-								IPackageFragmentRoot[] roots = project.findPackageFragmentRoots(entry);
-								for (int i = 0; i < roots.length; i++) {
-									IPackageFragmentRoot root = roots[i];
-									r = JavaRuntime.newArchiveRuntimeClasspathEntry(root.getPath(), entry.getSourceAttachmentPath(), entry.getSourceAttachmentRootPath(), entry.getAccessRules(), entry.getExtraAttributes(), entry.isExported());
-									if (isModularJVM) {
-										adjustClasspathProperty(r, entry);
-									}
-									r.setSourceAttachmentPath(entry.getSourceAttachmentPath());
-									r.setSourceAttachmentRootPath(entry.getSourceAttachmentRootPath());
-									if (!expandedPath.contains(r)) {
-										expandedPath.add(r);
+								if (!visitedEntries.contains(entry)) {
+									visitedEntries.add(entry);
+									IPackageFragmentRoot[] roots = project.findPackageFragmentRoots(entry);
+									for (int i = 0; i < roots.length; i++) {
+										IPackageFragmentRoot root = roots[i];
+										r = JavaRuntime.newArchiveRuntimeClasspathEntry(root.getPath(), entry.getSourceAttachmentPath(), entry.getSourceAttachmentRootPath(), entry.getAccessRules(), entry.getExtraAttributes(), entry.isExported());
+										if (isModularJVM) {
+											adjustClasspathProperty(r, entry);
+										}
+										r.setSourceAttachmentPath(entry.getSourceAttachmentPath());
+										r.setSourceAttachmentRootPath(entry.getSourceAttachmentRootPath());
+										if (!expandedPath.contains(r)) {
+											expandedPath.add(r);
+										}
 									}
 								}
 							} else {