Bug 544921 - [regression] Compilation failures after perf optimization

After the performance patch, errors of the form may happen:
The class file EnumMap<K,V> contains a signature
'Ljava/util/EnumMap<TK;TV;>.EnumMapIterator<Ljava/util/Map$Entry<TK;TV;>;>;'
ill-formed at position 27

Reentrant queries for member types caused trouble when sorting was still
in progress.

Also disabled memory-hungry tests in low memory env and increased bundle
version number.

Change-Id: I68edca65ade19a3166436b53ec2828dcf06bf0b2
Signed-off-by: Sebastian Zarnekow <sebastian.zarnekow@gmail.com>
Signed-off-by: Andrey Loskutov <loskutov@gmx.de>
diff --git a/org.eclipse.jdt.core.tests.builder/META-INF/MANIFEST.MF b/org.eclipse.jdt.core.tests.builder/META-INF/MANIFEST.MF
index ef6269e..92bb29b 100644
--- a/org.eclipse.jdt.core.tests.builder/META-INF/MANIFEST.MF
+++ b/org.eclipse.jdt.core.tests.builder/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %pluginName
 Bundle-SymbolicName: org.eclipse.jdt.core.tests.builder; singleton:=true
-Bundle-Version: 3.10.600.qualifier
+Bundle-Version: 3.10.700.qualifier
 Bundle-Vendor: %providerName
 Bundle-Localization: plugin
 Export-Package: org.eclipse.jdt.core.tests.builder
diff --git a/org.eclipse.jdt.core.tests.builder/pom.xml b/org.eclipse.jdt.core.tests.builder/pom.xml
index e0ce495..e612ab0 100644
--- a/org.eclipse.jdt.core.tests.builder/pom.xml
+++ b/org.eclipse.jdt.core.tests.builder/pom.xml
@@ -19,7 +19,7 @@
   </parent>
   <groupId>org.eclipse.jdt</groupId>
   <artifactId>org.eclipse.jdt.core.tests.builder</artifactId>
-  <version>3.10.600-SNAPSHOT</version>
+  <version>3.10.700-SNAPSHOT</version>
   <packaging>eclipse-test-plugin</packaging>
 
   <properties>
diff --git a/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/Bug544921Test.java b/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/Bug544921Test.java
index 1740794..5ffa764 100644
--- a/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/Bug544921Test.java
+++ b/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/Bug544921Test.java
@@ -16,6 +16,7 @@
 import org.eclipse.core.runtime.IPath;
 import org.eclipse.jdt.core.JavaModelException;
 import org.eclipse.jdt.core.tests.util.Util;
+
 import junit.framework.Test;
 
 public class Bug544921Test extends BuilderTests {
@@ -27,6 +28,27 @@
 		return buildTestSuite(Bug544921Test.class);
 	}
 
+	public void testCompilerRegression() throws JavaModelException, Exception {
+		IPath projectPath = env.addProject("Bug544921Test", "1.8"); //$NON-NLS-1$
+
+		env.addExternalJars(projectPath, Util.getJavaClassLibs());
+		env.addClass(projectPath, "a", "Test", //$NON-NLS-1$ //$NON-NLS-2$
+				"package a;\n" +
+				"import java.util.EnumMap;\n" +
+				"enum E {}\n" +
+				"public class Test {\n" +
+				"    Object x = new EnumMap<E, String>(E.class) {\n" +
+				"        static final long serialVersionUID = 1;\n" +
+				"        {\n" +
+				"            E.values();\n" +
+				"        }\n" +
+				"    };\n" +
+				"}" //$NON-NLS-1$
+				);
+		fullBuild();
+		expectingNoProblems();
+	}
+
 	public void testBuildLargeFile_01() throws JavaModelException, Exception {
 		IPath projectPath = env.addProject("Bug544921Test", "1.8"); //$NON-NLS-1$
 		scaffoldProject(projectPath, 1, 10, 64);
@@ -48,32 +70,46 @@
 		expectingNoProblems();
 	}
 
+	private boolean hasEnoughMemory(long required) {
+		long bytes = Runtime.getRuntime().maxMemory();
+		long megabytes = bytes / 1024 / 1024;
+		return megabytes > required;
+	}
+
 	public void testBuildLargeFile_04() throws JavaModelException, Exception {
-		IPath projectPath = env.addProject("Bug544921Test", "1.8"); //$NON-NLS-1$
-		scaffoldProject(projectPath, 4, 500, 64);
-		fullBuild();
-		expectingNoProblems();
+		if (hasEnoughMemory(2048)) {
+			IPath projectPath = env.addProject("Bug544921Test", "1.8"); //$NON-NLS-1$
+			scaffoldProject(projectPath, 4, 500, 64);
+			fullBuild();
+			expectingNoProblems();
+		}
 	}
 
 	public void testBuildLargeFile_05() throws JavaModelException, Exception {
-		IPath projectPath = env.addProject("Bug544921Test", "1.8"); //$NON-NLS-1$
-		scaffoldProject(projectPath, 5, 500, 64);
-		fullBuild();
-		expectingNoProblems();
+		if (hasEnoughMemory(2048)) {
+			IPath projectPath = env.addProject("Bug544921Test", "1.8"); //$NON-NLS-1$
+			scaffoldProject(projectPath, 5, 500, 64);
+			fullBuild();
+			expectingNoProblems();
+		}
 	}
 
 	public void testBuildLargeFile_08() throws JavaModelException, Exception {
-		IPath projectPath = env.addProject("Bug544921Test", "1.8"); //$NON-NLS-1$
-		scaffoldProject(projectPath, 8, 500, 64);
-		fullBuild();
-		expectingNoProblems();
+		if (hasEnoughMemory(2048)) {
+			IPath projectPath = env.addProject("Bug544921Test", "1.8"); //$NON-NLS-1$
+			scaffoldProject(projectPath, 8, 500, 64);
+			fullBuild();
+			expectingNoProblems();
+		}
 	}
 
-	public void _testBuildLargeFile_10() throws JavaModelException, Exception {
-		IPath projectPath = env.addProject("Bug544921Test", "1.8"); //$NON-NLS-1$
-		scaffoldProject(projectPath, 10, 500, 64);
-		fullBuild();
-		expectingNoProblems();
+	public void testBuildLargeFile_10() throws JavaModelException, Exception {
+		if (hasEnoughMemory(2048)) {
+			IPath projectPath = env.addProject("Bug544921Test", "1.8"); //$NON-NLS-1$
+			scaffoldProject(projectPath, 10, 500, 64);
+			fullBuild();
+			expectingNoProblems();
+		}
 	}
 
 	private void scaffoldProject(IPath projectPath, int maxPeripheral, int maxRegister, int maxFields) throws JavaModelException {
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java
index 50a3633..5299130 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/BinaryTypeBinding.java
@@ -48,7 +48,6 @@
 package org.eclipse.jdt.internal.compiler.lookup;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 
 import org.eclipse.jdt.core.compiler.CharOperation;
 import org.eclipse.jdt.internal.compiler.ast.Annotation;
@@ -59,7 +58,16 @@
 import org.eclipse.jdt.internal.compiler.classfmt.NonNullDefaultAwareTypeAnnotationWalker;
 import org.eclipse.jdt.internal.compiler.classfmt.TypeAnnotationWalker;
 import org.eclipse.jdt.internal.compiler.codegen.ConstantPool;
-import org.eclipse.jdt.internal.compiler.env.*;
+import org.eclipse.jdt.internal.compiler.env.ClassSignature;
+import org.eclipse.jdt.internal.compiler.env.EnumConstantSignature;
+import org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation;
+import org.eclipse.jdt.internal.compiler.env.IBinaryElementValuePair;
+import org.eclipse.jdt.internal.compiler.env.IBinaryField;
+import org.eclipse.jdt.internal.compiler.env.IBinaryMethod;
+import org.eclipse.jdt.internal.compiler.env.IBinaryNestedType;
+import org.eclipse.jdt.internal.compiler.env.IBinaryType;
+import org.eclipse.jdt.internal.compiler.env.IBinaryTypeAnnotation;
+import org.eclipse.jdt.internal.compiler.env.ITypeAnnotationWalker;
 import org.eclipse.jdt.internal.compiler.impl.BooleanConstant;
 import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
 import org.eclipse.jdt.internal.compiler.impl.Constant;
@@ -1287,39 +1295,30 @@
 		return memberType == null ? null : this.environment.createMemberType(memberType, this);
 	}
 
-	ReferenceBinding[] members = sortedMemberTypes();
-	int memberTypeIndex = unresolvedTypesAwareBinarySearch(typeName, members);
-	if (memberTypeIndex >= 0) {
-		ReferenceBinding memberType = members[memberTypeIndex];
-		if (memberType instanceof UnresolvedReferenceBinding) {
-			return members[memberTypeIndex] = (ReferenceBinding) resolveType(memberType, this.environment, false /* no raw conversion for now */);
+	ReferenceBinding[] members = maybeSortedMemberTypes();
+	// do not try to binary search while we are still resolving and the array is not necessarily sorted
+	if (!this.memberTypesSorted) {
+		for (int i = members.length; --i >= 0;) {
+		    ReferenceBinding memberType = members[i];
+		    if (memberType instanceof UnresolvedReferenceBinding) {
+				char[] name = memberType.sourceName; // source name is qualified with enclosing type name
+				int prefixLength = this.compoundName[this.compoundName.length - 1].length + 1; // enclosing$
+				if (name.length == (prefixLength + typeName.length)) // enclosing $ typeName
+					if (CharOperation.fragmentEquals(typeName, name, prefixLength, true)) // only check trailing portion
+						return members[i] = (ReferenceBinding) resolveType(memberType, this.environment, false /* no raw conversion for now */);
+		    } else if (CharOperation.equals(typeName, memberType.sourceName)) {
+		        return memberType;
+		    }
 		}
-		return memberType;
+		return null;
+	}
+	int memberTypeIndex = ReferenceBinding.binarySearch(typeName, members);
+	if (memberTypeIndex >= 0) {
+		return members[memberTypeIndex];
 	}
 	return null;
 }
-private int unresolvedTypesAwareBinarySearch(char[] name, ReferenceBinding[] sortedMemberTypes) {
-	if (sortedMemberTypes == null)
-		return -1;
-	int max = sortedMemberTypes.length;
-	if (max == 0)
-		return -1;
-	int left = 0, right = max - 1, nameLength = name.length;
-	int mid = 0;
-	char[] midName;
-	while (left <= right) {
-		mid = left + (right - left) /2;
-		int compare = compare(name, midName = getSourceName(sortedMemberTypes[mid]), nameLength, midName.length);
-		if (compare < 0) {
-			right = mid-1;
-		} else if (compare > 0) {
-			left = mid+1;
-		} else {
-			return mid;
-		}
-	}
-	return -1;
-}
+
 // NOTE: the return type, arg & exception types of each method of a binary type are resolved when needed
 @Override
 public MethodBinding[] getMethods(char[] selector) {
@@ -1563,44 +1562,29 @@
 	}
 	
 	if ((this.tagBits & TagBits.HasUnresolvedMemberTypes) == 0) {
-		return sortedMemberTypes();
+		return maybeSortedMemberTypes();
 	}
 	for (int i = this.memberTypes.length; --i >= 0;)
 		this.memberTypes[i] = (ReferenceBinding) resolveType(this.memberTypes[i], this.environment, false /* no raw conversion for now */);
 	this.tagBits &= ~TagBits.HasUnresolvedMemberTypes;
-	return sortedMemberTypes();
+	return maybeSortedMemberTypes();
 }
 
-private ReferenceBinding[] sortedMemberTypes() {
+private ReferenceBinding[] maybeSortedMemberTypes() {
+	// do not try to sort while we are still resolving
+	if ((this.tagBits & TagBits.HasUnresolvedMemberTypes) != 0) {
+		return this.memberTypes;
+	}
 	if (!this.memberTypesSorted) {
 		// lazily sort member types
 		int length = this.memberTypes.length;
 		if (length > 1)
-			unresolvedAwareSortMemberTypes(this.memberTypes, 0, length);
+			sortMemberTypes(this.memberTypes, 0, length);
 		this.memberTypesSorted = true;
 	}
 	return this.memberTypes;
 }
 
-private void unresolvedAwareSortMemberTypes(ReferenceBinding[] sortedMemberTypes, int left, int right) {
-	Arrays.sort(sortedMemberTypes, left, right, this::unresolvedAwareCompare);
-}
-
-private int unresolvedAwareCompare(ReferenceBinding o1, ReferenceBinding o2) {
-	char[] n1 = getSourceName(o1);
-	char[] n2 = getSourceName(o2);
-	return ReferenceBinding.compare(n1, n2, n1.length, n2.length);
-}
-
-private char[] getSourceName(ReferenceBinding memberType) {
-	if (memberType instanceof UnresolvedReferenceBinding) {
-		char[] name = memberType.sourceName; // source name is qualified with enclosing type name
-		int prefixLength = this.compoundName[this.compoundName.length - 1].length + 1; // enclosing$
-		return CharOperation.subarray(name, prefixLength, name.length);
-    }
-	return memberType.sourceName;
-}
-
 // NOTE: the return type, arg & exception types of each method of a binary type are resolved when needed
 @Override
 public MethodBinding[] methods() {
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java
index 241874b..5c1e31f 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java
@@ -235,6 +235,19 @@
 }
 
 /**
+ * Sort the member types using a quicksort
+ */
+static void sortMemberTypes(ReferenceBinding[] sortedMemberTypes, int left, int right) {
+	Arrays.sort(sortedMemberTypes, left, right, BASIC_MEMBER_TYPES_COMPARATOR);
+}
+
+static final Comparator<ReferenceBinding> BASIC_MEMBER_TYPES_COMPARATOR = (ReferenceBinding o1, ReferenceBinding o2) -> {
+	char[] n1 = o1.sourceName;
+	char[] n2 = o2.sourceName;
+	return ReferenceBinding.compare(n1, n2, n1.length, n2.length);
+};
+
+/**
  * Return the array of resolvable fields (resilience)
  */
 public FieldBinding[] availableFields() {
@@ -1055,14 +1068,14 @@
  */
 public ReferenceBinding getMemberType(char[] typeName) {
 	ReferenceBinding[] memberTypes = memberTypes();
-	int memberTypeIndex = ReferenceBinding.binarySearch(typeName, memberTypes);
+	int memberTypeIndex = binarySearch(typeName, memberTypes);
 	if (memberTypeIndex >= 0) {
 		return memberTypes[memberTypeIndex];
 	}
 	return null;
 }
 
-private static int binarySearch(char[] name, ReferenceBinding[] sortedMemberTypes) {
+static int binarySearch(char[] name, ReferenceBinding[] sortedMemberTypes) {
 	if (sortedMemberTypes == null)
 		return -1;
 	int max = sortedMemberTypes.length;
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java
index 5158fb9..003e110 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/SourceTypeBinding.java
@@ -53,9 +53,7 @@
  *******************************************************************************/
 package org.eclipse.jdt.internal.compiler.lookup;
 
-import java.util.Arrays;
 import java.util.Collection;
-import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -1545,19 +1543,6 @@
 	return this.memberTypes;
 }
 
-/**
- * Sort the member types using a quicksort
- */
-private static void sortMemberTypes(ReferenceBinding[] sortedMemberTypes, int left, int right) {
-	Arrays.sort(sortedMemberTypes, left, right, BASIC_MEMBER_TYPES_COMPARATOR);
-}
-
-private static final Comparator<ReferenceBinding> BASIC_MEMBER_TYPES_COMPARATOR = (ReferenceBinding o1, ReferenceBinding o2) -> {
-	char[] n1 = o1.sourceName;
-	char[] n2 = o2.sourceName;
-	return ReferenceBinding.compare(n1, n2, n1.length, n2.length);
-};
-
 @Override
 public boolean hasMemberTypes() {
 	if (!isPrototype())