Bug 564047 - [15] JEP 360 - Sealed Types - BinaryTypeBinding

Change-Id: Idac6c2fa41b5a8637561745f094ffba1db6a25fd
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest_15.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest_15.java
new file mode 100644
index 0000000..5cffd46
--- /dev/null
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/BatchCompilerTest_15.java
@@ -0,0 +1,109 @@
+/*******************************************************************************
+ * Copyright (c) 2018, 2020 IBM Corporation.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * This is an implementation of an early-draft specification developed under the Java
+ * Community Process (JCP) and is made available for testing and evaluation purposes
+ * only. The code is not compatible with any specification of the JCP.
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.jdt.core.tests.compiler.regression;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.tests.util.Util;
+import junit.framework.Test;
+
+@SuppressWarnings({ "rawtypes" })
+public class BatchCompilerTest_15 extends AbstractBatchCompilerTest {
+
+	static {
+//		TESTS_NAMES = new String[] { "test440477" };
+//		TESTS_NUMBERS = new int[] { 306 };
+//		TESTS_RANGE = new int[] { 298, -1 };
+	}
+
+	/**
+	 * This test suite only needs to be run on one compliance.
+	 * As it includes some specific 1.5 tests, it must be used with a least a 1.5 VM
+	 * and not be duplicated in general test suite.
+	 * @see TestAll
+	 */
+	public static Test suite() {
+		return buildMinimalComplianceTestSuite(testClass(), F_15);
+	}
+	public static Class testClass() {
+		return BatchCompilerTest_15.class;
+	}
+	public BatchCompilerTest_15(String name) {
+		super(name);
+	}
+	public void testBug564047_001(){
+		String currentWorkingDirectoryPath = System.getProperty("user.dir");
+		if (currentWorkingDirectoryPath == null) {
+			System.err.println("BatchCompilerTest#testBug564047_001 could not access the current working directory " + currentWorkingDirectoryPath);
+		} else if (!new File(currentWorkingDirectoryPath).isDirectory()) {
+			System.err.println("BatchCompilerTest#testBug564047_001 current working directory is not a directory " + currentWorkingDirectoryPath);
+		} else {
+			String lib1Path = currentWorkingDirectoryPath + File.separator + "lib1.jar";
+			try {
+			Util.createJar(
+					new String[] {
+						"p/Y.java",
+						"package p;\n" +
+						"public sealed class Y permits Z{}",
+						"p/Z.java",
+						"package p;\n" +
+						"public final class Z extends Y{}",
+					},
+					lib1Path,
+					JavaCore.VERSION_15,
+					true);
+			this.runNegativeTest(
+					new String[] {
+						"src/p/X.java",
+						"package p;\n" +
+						"public class X extends Y {\n" +
+						"  public static void main(String[] args){\n" +
+						"     System.out.println(0);\n" +
+						"  }\n" +
+						"}",
+					},
+			        "\"" + OUTPUT_DIR +  File.separator + "src/p/X.java\""
+					+ " -cp " + lib1Path  // relative
+					+ " -sourcepath \"" + OUTPUT_DIR +  File.separator + "src\""
+			        + " --release 15 --enable-preview -g -preserveAllLocals"
+			        + " -proceedOnError -referenceInfo"
+			        + " -d \"" + OUTPUT_DIR + File.separator + "bin\" ",
+			        "",
+			        "----------\n" +
+			        "1. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/src/p/X.java (at line 2)\n" +
+			        "	public class X extends Y {\n" +
+			        "	                       ^\n" +
+			        "The class X with a sealed direct superclass or a sealed direct superinterface Y should be declared either final, sealed, or non-sealed\n" +
+			        "----------\n" +
+			        "2. ERROR in ---OUTPUT_DIR_PLACEHOLDER---/src/p/X.java (at line 2)\n" +
+			        "	public class X extends Y {\n" +
+			        "	                       ^\n" +
+			        "The type X extending a sealed class Y should be a permitted subtype of Y\n" +
+			        "----------\n" +
+			        "2 problems (2 errors)\n",
+			        true);
+			} catch (IOException e) {
+				System.err.println("BatchCompilerTest#testBug563430_001 could not write to current working directory " + currentWorkingDirectoryPath);
+			} finally {
+				new File(lib1Path).delete();
+			}
+		}
+	}
+}
diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SealedTypes15Tests.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SealedTypes15Tests.java
index 3e37a42..19ce0df 100644
--- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SealedTypes15Tests.java
+++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/SealedTypes15Tests.java
@@ -20,6 +20,8 @@
 import java.io.IOException;
 import java.util.Map;
 
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jdt.core.JavaCore;
 import org.eclipse.jdt.core.ToolFactory;
 import org.eclipse.jdt.core.tests.util.Util;
 import org.eclipse.jdt.core.util.ClassFileBytesDisassembler;
@@ -1353,4 +1355,54 @@
 				"The class Y with a sealed direct superclass or a sealed direct superinterface X should be declared either final, sealed, or non-sealed\n" +
 				"----------\n");
 	}
+	public void testBug564047_001() throws CoreException, IOException {
+		String outputDirectory = Util.getOutputDirectory();
+		String lib1Path = outputDirectory + File.separator + "lib1.jar";
+		try {
+		Util.createJar(
+				new String[] {
+					"p/Y.java",
+					"package p;\n" +
+					"public sealed class Y permits Z{}",
+					"p/Z.java",
+					"package p;\n" +
+					"public final class Z extends Y{}",
+				},
+				lib1Path,
+				JavaCore.VERSION_15,
+				true);
+		String[] libs = getDefaultClassPaths();
+		int len = libs.length;
+		System.arraycopy(libs, 0, libs = new String[len+1], 0, len);
+		libs[len] = lib1Path;
+		this.runNegativeTest(
+				new String[] {
+					"src/p/X.java",
+					"package p;\n" +
+					"public class X extends Y {\n" +
+					"  public static void main(String[] args){\n" +
+					"     System.out.println(0);\n" +
+					"  }\n" +
+					"}",
+				},
+				"----------\n" +
+				"1. ERROR in src\\p\\X.java (at line 2)\n" +
+				"	public class X extends Y {\n" +
+				"	                       ^\n" +
+				"The class X with a sealed direct superclass or a sealed direct superinterface Y should be declared either final, sealed, or non-sealed\n" +
+				"----------\n" +
+				"2. ERROR in src\\p\\X.java (at line 2)\n" +
+				"	public class X extends Y {\n" +
+				"	                       ^\n" +
+				"The type X extending a sealed class Y should be a permitted subtype of Y\n" +
+				"----------\n",
+				libs,
+		        true);
+		} catch (IOException e) {
+			System.err.println("could not write to current working directory ");
+		} finally {
+			new File(lib1Path).delete();
+		}
+
+	}
 }
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AbstractJavaModelTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AbstractJavaModelTests.java
index 0f01891..1925861 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AbstractJavaModelTests.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/model/AbstractJavaModelTests.java
@@ -1546,6 +1546,9 @@
 	protected IJavaProject createJava14Project(String name, String[] srcFolders) throws CoreException {
 		return createJava9ProjectWithJREAttributes(name, srcFolders, null, "14");
 	}
+	protected IJavaProject createJava15Project(String name, String[] srcFolders) throws CoreException {
+		return createJava9ProjectWithJREAttributes(name, srcFolders, null, "15");
+	}
 	protected IJavaProject createJava9ProjectWithJREAttributes(String name, String[] srcFolders, IClasspathAttribute[] attributes) throws CoreException {
 		return createJava9ProjectWithJREAttributes(name, srcFolders, attributes, "9");
 	}
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 5938323..2218fa4 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
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2019 IBM Corporation and others.
+ * Copyright (c) 2000, 2020 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -8,6 +8,10 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  *
+ * This is an implementation of an early-draft specification developed under the Java
+ * Community Process (JCP) and is made available for testing and evaluation purposes
+ * only. The code is not compatible with any specification of the JCP.
+ *
  * Contributors:
  *     IBM Corporation - initial API and implementation
  *     Stephan Herrmann - Contributions for
@@ -97,6 +101,7 @@
 	protected ReferenceBinding superclass;
 	protected ReferenceBinding enclosingType;
 	protected ReferenceBinding[] superInterfaces;
+	protected ReferenceBinding[] permittedSubtypes;
 	protected FieldBinding[] fields;
 	protected MethodBinding[] methods;
 	protected ReferenceBinding[] memberTypes;
@@ -261,6 +266,7 @@
 	this.superclass = prototype.superclass;
 	this.enclosingType = prototype.enclosingType;
 	this.superInterfaces = prototype.superInterfaces;
+	this.permittedSubtypes = prototype.permittedSubtypes;
 	this.fields = prototype.fields;
 	this.methods = prototype.methods;
 	this.memberTypes = prototype.memberTypes;
@@ -442,6 +448,7 @@
 		// and still want to use binaries passed that point (e.g. type hierarchy resolver, see bug 63748).
 		this.typeVariables = Binding.NO_TYPE_VARIABLES;
 		this.superInterfaces = Binding.NO_SUPERINTERFACES;
+		this.permittedSubtypes = Binding.NO_PERMITTEDTYPES;
 
 		// must retrieve member types in case superclass/interfaces need them
 		this.memberTypes = Binding.NO_MEMBER_TYPES;
@@ -526,6 +533,19 @@
 					this.tagBits |= TagBits.HasUnresolvedSuperinterfaces;
 				}
 			}
+
+			this.permittedSubtypes = Binding.NO_PERMITTEDTYPES;
+			char[][] permittedSubtypeNames = binaryType.getPermittedSubtypeNames();
+			if (permittedSubtypeNames != null) {
+				int size = permittedSubtypeNames.length;
+				if (size > 0) {
+					this.permittedSubtypes = new ReferenceBinding[size];
+					for (short i = 0; i < size; i++)
+						// attempt to find each superinterface if it exists in the cache (otherwise - resolve it when requested)
+						this.permittedSubtypes[i] = this.environment.getTypeFromConstantPoolName(permittedSubtypeNames[i], 0, -1, false, missingTypeNames, toplevelWalker.toSupertype(i, superclassName));
+					this.extendedTagBits |= ExtendedTagBits.HasUnresolvedPermittedSubtypes;
+				}
+			}
 		} else {
 			// attempt to find the superclass if it exists in the cache (otherwise - resolve it when requested)
 			this.superclass = (ReferenceBinding) this.environment.getTypeFromTypeSignature(wrapper, typeVars, this, missingTypeNames,
@@ -544,6 +564,20 @@
 				types.toArray(this.superInterfaces);
 				this.tagBits |= TagBits.HasUnresolvedSuperinterfaces;
 			}
+
+			this.permittedSubtypes = Binding.NO_PERMITTEDTYPES;
+			if (!wrapper.atEnd()) {
+				// attempt to find each permitted type if it exists in the cache (otherwise - resolve it when requested)
+				java.util.ArrayList types = new java.util.ArrayList(2);
+				short rank = 0;
+				do {
+					types.add(this.environment.getTypeFromTypeSignature(wrapper, typeVars, this, missingTypeNames, toplevelWalker.toSupertype(rank++, wrapper.peekFullType())));
+				} while (!wrapper.atEnd());
+				this.permittedSubtypes = new ReferenceBinding[types.size()];
+				types.toArray(this.permittedSubtypes);
+				this.extendedTagBits |= ExtendedTagBits.HasUnresolvedPermittedSubtypes;
+			}
+
 		}
 		boolean canUseNullTypeAnnotations = this.environment.globalOptions.isAnnotationBasedNullAnalysisEnabled && this.environment.globalOptions.sourceLevel >= ClassFileConstants.JDK1_8;
 		if (canUseNullTypeAnnotations && this.externalAnnotationStatus.isPotentiallyUnannotatedLib()) {
@@ -556,6 +590,12 @@
 						break;
 					}
 				}
+				for (TypeBinding permsub : this.permittedSubtypes) {
+					if (permsub.hasNullTypeAnnotations()) {
+						this.externalAnnotationStatus = ExternalAnnotationStatus.TYPE_IS_ANNOTATED;
+						break;
+					}
+				}
 			}
 		}
 
@@ -1267,6 +1307,7 @@
 				refScope.recordTypeReference(this.superclass);
 			return this.superclass.getExactMethod(selector, argumentTypes, refScope);
 		}
+		// NOTE: not adding permitted types here since the search is up the hierarchy while permitted ones are down.
 	}
 	return null;
 }
@@ -1630,6 +1671,12 @@
 }
 
 @Override
+public boolean isSealed() {
+	ReferenceBinding[] permittedSubTypes = permittedTypes();
+	return !(permittedSubTypes == null || permittedSubTypes == Binding.NO_PERMITTEDTYPES);
+}
+
+@Override
 public ReferenceBinding containerAnnotationType() {
 	if (!isPrototype()) throw new IllegalStateException();
 	if (this.containerAnnotationType instanceof UnresolvedReferenceBinding) {
@@ -2199,6 +2246,16 @@
 	this.tagBits &= ~TagBits.HasUnresolvedSuperinterfaces;
 	return this.superInterfaces;
 }
+//NOTE: permitted subtypes of binary types are resolved when needed
+@Override
+public ReferenceBinding[] permittedTypes() {
+
+	if (!isPrototype()) {
+		return this.permittedSubtypes = this.prototype.permittedTypes();
+	}
+	// Note: unlike for superinterfaces() hierarchy check not required here since these are subtypes
+	return this.permittedSubtypes;
+}
 @Override
 public TypeVariableBinding[] typeVariables() {
 
@@ -2267,6 +2324,19 @@
 		buffer.append("NULL SUPERINTERFACES"); //$NON-NLS-1$
 	}
 
+	if (this.permittedSubtypes != null) {
+		if (this.permittedSubtypes != Binding.NO_PERMITTEDTYPES) {
+			buffer.append("\n\tpermits : "); //$NON-NLS-1$
+			for (int i = 0, length = this.permittedSubtypes.length; i < length; i++) {
+				if (i  > 0)
+					buffer.append(", "); //$NON-NLS-1$
+				buffer.append((this.permittedSubtypes[i] != null) ? this.permittedSubtypes[i].debugName() : "NULL TYPE"); //$NON-NLS-1$
+			}
+		}
+	} else {
+		buffer.append("NULL PERMITTEDSUBTYPES"); //$NON-NLS-1$
+	}
+
 	if (this.enclosingType != null) {
 		buffer.append("\n\tenclosing type : "); //$NON-NLS-1$
 		buffer.append(this.enclosingType.debugName());
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ExtendedTagBits.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ExtendedTagBits.java
index 42a43d4..626530a 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ExtendedTagBits.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ExtendedTagBits.java
@@ -8,6 +8,10 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  *
+ * This is an implementation of an early-draft specification developed under the Java
+ * Community Process (JCP) and is made available for testing and evaluation purposes
+ * only. The code is not compatible with any specification of the JCP.
+ *
  * Contributors:
  *     IBM Corporation - initial API and implementation
  *******************************************************************************/
@@ -18,5 +22,6 @@
 public interface ExtendedTagBits {
 
 	int AreRecordComponentsComplete = ASTNode.Bit1;
+	int HasUnresolvedPermittedSubtypes = ASTNode.Bit2;
 
 }
diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MissingTypeBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MissingTypeBinding.java
index 2ba5a78..1d1e1ff 100644
--- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MissingTypeBinding.java
+++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/MissingTypeBinding.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2015 IBM Corporation and others.
+ * Copyright (c) 2000, 2020 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -8,6 +8,10 @@
  *
  * SPDX-License-Identifier: EPL-2.0
  *
+ * This is an implementation of an early-draft specification developed under the Java
+ * Community Process (JCP) and is made available for testing and evaluation purposes
+ * only. The code is not compatible with any specification of the JCP.
+ *
  * Contributors:
  *     IBM Corporation - initial API and implementation
  *******************************************************************************/
@@ -38,6 +42,7 @@
 	this.modifiers = ClassFileConstants.AccPublic;
 	this.superclass = null; // will be fixed up using #setMissingSuperclass(...)
 	this.superInterfaces = Binding.NO_SUPERINTERFACES;
+	this.permittedSubtypes = Binding.NO_PERMITTEDTYPES;
 	this.typeVariables = Binding.NO_TYPE_VARIABLES;
 	this.memberTypes = Binding.NO_MEMBER_TYPES;
 	this.fields = Binding.NO_FIELDS;
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 6fb6ec2..bcabd64 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
@@ -1523,7 +1523,7 @@
 /**
  * Answer true if the receiver has sealed modifier
  */
-public final boolean isSealed() {
+public boolean isSealed() {
 	return (this.modifiers & ExtraCompilerModifiers.AccSealed) != 0;
 }