From 98b53c06cfffe7b2e21968546b4aa1523f31132e Mon Sep 17 00:00:00 2001 From: Jim Laskey Date: Mon, 5 Jun 2023 18:45:39 +0000 Subject: [PATCH] 8306112: Implementation of JEP 445: Unnamed Classes and Instance Main Methods (Preview) 8308613: javax.lang.model updates for JEP 445 (preview) 8308913: Update core reflection for JEP 445 (preview) Co-authored-by: Maurizio Cimadamore Co-authored-by: Joe Darcy Co-authored-by: Jan Lahoda Co-authored-by: Jim Laskey Co-authored-by: Adam Sotona Reviewed-by: mcimadamore, vromero, darcy --- make/CompileInterimLangtools.gmk | 1 + .../share/classes/java/lang/Class.java | 61 ++- .../jdk/internal/javac/PreviewFeature.java | 2 + .../jdk/internal/misc/MainMethodFinder.java | 169 ++++++++ .../classes/sun/launcher/LauncherHelper.java | 64 ++- .../launcher/resources/launcher.properties | 9 +- src/java.base/share/native/libjli/java.c | 57 ++- .../javax/annotation/processing/Filer.java | 17 +- .../javax/lang/model/element/TypeElement.java | 25 +- .../com/sun/tools/javac/code/ClassFinder.java | 3 + .../com/sun/tools/javac/code/Flags.java | 7 +- .../com/sun/tools/javac/code/Preview.java | 1 + .../com/sun/tools/javac/code/Source.java | 3 +- .../com/sun/tools/javac/code/Symbol.java | 21 +- .../com/sun/tools/javac/comp/Attr.java | 4 + .../com/sun/tools/javac/comp/Check.java | 32 +- .../com/sun/tools/javac/comp/Enter.java | 7 +- .../com/sun/tools/javac/comp/Resolve.java | 3 - .../com/sun/tools/javac/jvm/ClassReader.java | 5 + .../com/sun/tools/javac/launcher/Main.java | 80 +++- .../sun/tools/javac/parser/JavacParser.java | 405 +++++++++++++----- .../com/sun/tools/javac/parser/Scanner.java | 2 +- .../sun/tools/javac/parser/VirtualParser.java | 189 ++++++++ .../javac/processing/PrintingProcessor.java | 24 +- .../tools/javac/resources/compiler.properties | 15 +- .../tools/javac/resources/launcher.properties | 8 + .../com/sun/tools/javac/tree/TreeMaker.java | 6 +- .../com/sun/tools/javac/util/Convert.java | 5 +- .../com/sun/tools/javac/util/Names.java | 4 +- test/jdk/tools/launcher/InstanceMainTest.java | 190 ++++++++ .../javac/diags/examples/UnnamedClass.java | 29 ++ .../examples/UnnamedClassBad-Filename.java | 30 ++ .../examples/UnnamedClassHasPackage.java | 32 ++ .../diags/examples/UnnamedClassNoMain.java | 30 ++ .../javac/launcher/SourceLauncherTest.java | 2 +- .../processing/model/element/Anonymous.java | 42 ++ .../model/element/TestUnnamedClass.java | 200 +++++++++ .../javac/unnamed/UnnamedClassRecovery.java | 8 + .../javac/unnamed/UnnamedClassRecovery.out | 4 + .../tools/javac/unnamedclass/NestedEnum.java | 34 ++ 40 files changed, 1654 insertions(+), 176 deletions(-) create mode 100644 src/java.base/share/classes/jdk/internal/misc/MainMethodFinder.java create mode 100644 src/jdk.compiler/share/classes/com/sun/tools/javac/parser/VirtualParser.java create mode 100644 test/jdk/tools/launcher/InstanceMainTest.java create mode 100644 test/langtools/tools/javac/diags/examples/UnnamedClass.java create mode 100644 test/langtools/tools/javac/diags/examples/UnnamedClassBad-Filename.java create mode 100644 test/langtools/tools/javac/diags/examples/UnnamedClassHasPackage.java create mode 100644 test/langtools/tools/javac/diags/examples/UnnamedClassNoMain.java create mode 100644 test/langtools/tools/javac/processing/model/element/Anonymous.java create mode 100644 test/langtools/tools/javac/processing/model/element/TestUnnamedClass.java create mode 100644 test/langtools/tools/javac/unnamed/UnnamedClassRecovery.java create mode 100644 test/langtools/tools/javac/unnamed/UnnamedClassRecovery.out create mode 100644 test/langtools/tools/javac/unnamedclass/NestedEnum.java diff --git a/make/CompileInterimLangtools.gmk b/make/CompileInterimLangtools.gmk index 51263fde3bd..bbc2d103696 100644 --- a/make/CompileInterimLangtools.gmk +++ b/make/CompileInterimLangtools.gmk @@ -98,6 +98,7 @@ define SetupInterimModule EXCLUDES := sun javax/tools/snippet-files, \ EXCLUDE_FILES := $(TOPDIR)/src/$1/share/classes/module-info.java \ $(TOPDIR)/src/$1/share/classes/javax/tools/ToolProvider.java \ + $(TOPDIR)/src/$1/share/classes/com/sun/tools/javac/launcher/Main.java \ Standard.java, \ EXTRA_FILES := $(BUILDTOOLS_OUTPUTDIR)/gensrc/$1.interim/module-info.java \ $($1.interim_EXTRA_FILES), \ diff --git a/src/java.base/share/classes/java/lang/Class.java b/src/java.base/share/classes/java/lang/Class.java index 3031823045f..2f7eedecd28 100644 --- a/src/java.base/share/classes/java/lang/Class.java +++ b/src/java.base/share/classes/java/lang/Class.java @@ -70,8 +70,10 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; +import jdk.internal.javac.PreviewFeature; import jdk.internal.loader.BootLoader; import jdk.internal.loader.BuiltinClassLoader; +import jdk.internal.misc.PreviewFeatures; import jdk.internal.misc.Unsafe; import jdk.internal.module.Resources; import jdk.internal.reflect.CallerSensitive; @@ -81,6 +83,7 @@ import jdk.internal.reflect.Reflection; import jdk.internal.reflect.ReflectionFactory; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.IntrinsicCandidate; + import sun.invoke.util.Wrapper; import sun.reflect.generics.factory.CoreReflectionFactory; import sun.reflect.generics.factory.GenericsFactory; @@ -157,7 +160,8 @@ import sun.reflect.misc.ReflectUtil; * other members are the classes and interfaces whose declarations are * enclosed within the top-level class declaration. * - *

A class or interface created by the invocation of + *

Hidden Classes

+ * A class or interface created by the invocation of * {@link java.lang.invoke.MethodHandles.Lookup#defineHiddenClass(byte[], boolean, MethodHandles.Lookup.ClassOption...) * Lookup::defineHiddenClass} is a {@linkplain Class#isHidden() hidden} * class or interface. @@ -185,6 +189,31 @@ import sun.reflect.misc.ReflectUtil; * a class or interface is hidden has no bearing on the characteristics * exposed by the methods of class {@code Class}. * + *

Unnamed Classes

+ * + * A {@code class} file representing an {@linkplain #isUnnamedClass unnamed class} + * is generated by a Java compiler from a source file for an unnamed class. + * The {@code Class} object representing an unnamed class is top-level, + * {@linkplain #isSynthetic synthetic}, and {@code final}. While an + * unnamed class does not have a name in its Java source + * form, several of the name-related methods of {@code java.lang.Class} + * do return non-null and non-empty results for the {@code Class} + * object representing an unnamed class. + * + * Conventionally, a Java compiler, starting from a source file for an + * unnamed class, say {@code HelloWorld.java}, creates a + * similarly-named {@code class} file, {@code HelloWorld.class}, where + * the class stored in that {@code class} file is named {@code + * "HelloWorld"}, matching the base names of the source and {@code + * class} files. + * + * For the {@code Class} object of an unnamed class {@code + * HelloWorld}, the methods to get the {@linkplain #getName name} and + * {@linkplain #getTypeName type name} return results + * equal to {@code "HelloWorld"}. The {@linkplain #getSimpleName + * simple name} of such an unnamed class is the empty string and the + * {@linkplain #getCanonicalName canonical name} is {@code null}. + * * @param the type of the class modeled by this {@code Class} * object. For example, the type of {@code String.class} is {@code * Class}. Use {@code Class} if the class being modeled is @@ -1717,7 +1746,7 @@ public final class Class implements java.io.Serializable, /** * Returns the simple name of the underlying class as given in the * source code. An empty string is returned if the underlying class is - * {@linkplain #isAnonymousClass() anonymous}. + * {@linkplain #isAnonymousClass() anonymous} or {@linkplain #isUnnamedClass() unnamed}. * A {@linkplain #isSynthetic() synthetic class}, one not present * in source code, can have a non-empty name including special * characters, such as "{@code $}". @@ -1730,6 +1759,9 @@ public final class Class implements java.io.Serializable, * @since 1.5 */ public String getSimpleName() { + if (isUnnamedClass()) { + return ""; + } ReflectionData rd = reflectionData(); String simpleName = rd.simpleName; if (simpleName == null) { @@ -1779,6 +1811,7 @@ public final class Class implements java.io.Serializable, *
    *
  • a {@linkplain #isLocalClass() local class} *
  • a {@linkplain #isAnonymousClass() anonymous class} + *
  • an {@linkplain #isUnnamedClass() unnamed class} *
  • a {@linkplain #isHidden() hidden class} *
  • an array whose component type does not have a canonical name
  • *
@@ -1798,6 +1831,9 @@ public final class Class implements java.io.Serializable, * @since 1.5 */ public String getCanonicalName() { + if (isUnnamedClass()) { + return null; + } ReflectionData rd = reflectionData(); String canonicalName = rd.canonicalName; if (canonicalName == null) { @@ -1832,12 +1868,33 @@ public final class Class implements java.io.Serializable, } } + /** + * {@return {@code true} if and only if the underlying class + * is an unnamed class} + * + * @apiNote + * An unnamed class is not an {@linkplain #isAnonymousClass anonymous class}. + * + * @since 21 + * + * @jls 7.3 Compilation Units + */ + @PreviewFeature(feature=PreviewFeature.Feature.UNNAMED_CLASSES, + reflective=true) + public boolean isUnnamedClass() { + return PreviewFeatures.isEnabled() && isSynthetic() + && isTopLevelClass() + && Modifier.isFinal(getModifiers()); + } + + /** * Returns {@code true} if and only if the underlying class * is an anonymous class. * * @apiNote * An anonymous class is not a {@linkplain #isHidden() hidden class}. + * An anonymous class is not an {@linkplain #isUnnamedClass() unnamed class}. * * @return {@code true} if and only if this class is an anonymous class. * @since 1.5 diff --git a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java index d4cd35f2f2e..b61c3ad8179 100644 --- a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java +++ b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java @@ -72,6 +72,8 @@ public @interface PreviewFeature { STRING_TEMPLATES, @JEP(number=443, title="Unnamed Patterns and Variables") UNNAMED, + @JEP(number=445, title="Unnamed Classes and Instance Main Methods") + UNNAMED_CLASSES, /** * A key for testing. */ diff --git a/src/java.base/share/classes/jdk/internal/misc/MainMethodFinder.java b/src/java.base/share/classes/jdk/internal/misc/MainMethodFinder.java new file mode 100644 index 00000000000..be4ebc38238 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/misc/MainMethodFinder.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.misc; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; + +public class MainMethodFinder { + private static boolean correctArgs(Method method) { + int argc = method.getParameterCount(); + + return argc == 0 || argc == 1 && method.getParameterTypes()[0] == String[].class; + } + + /** + * Gather all the "main" methods in the class hierarchy. + * + * @param refc the main class or super class + * @param mains accumulated main methods + * @param isMainClass the class is the main class and not a super class + */ + private static void gatherMains(Class refc, List mains, boolean isMainClass) { + if (refc != null && refc != Object.class) { + for (Method method : refc.getDeclaredMethods()) { + int mods = method.getModifiers(); + // Must be named "main", public|protected|package-private, not synthetic (bridge) and either + // no arguments or one string array argument. Only statics in the Main class are acceptable. + if ("main".equals(method.getName()) && + !method.isSynthetic() && + !Modifier.isPrivate(mods) && + correctArgs(method) && + (isMainClass || !Modifier.isStatic(mods))) + { + mains.add(method); + } + } + + gatherMains(refc.getSuperclass(), mains, false); + } + } + + /** + * Comparator for two methods. + * Priority order is; + * sub-class < super-class. + * static < non-static, + * string arg < no arg and + * + * @param a first method + * @param b second method + * + * @return -1, 0 or 1 to represent higher priority. equals priority or lesser priority. + */ + private static int compareMethods(Method a, Method b) { + Class aClass = a.getDeclaringClass(); + Class bClass = b.getDeclaringClass(); + + if (aClass != bClass) { + if (bClass.isAssignableFrom(aClass)) { + return -1; + } else { + return 1; + } + } + + int aMods = a.getModifiers(); + int bMods = b.getModifiers(); + boolean aIsStatic = Modifier.isStatic(aMods); + boolean bIsStatic = Modifier.isStatic(bMods); + + if (aIsStatic && !bIsStatic) { + return -1; + } else if (!aIsStatic && bIsStatic) { + return 1; + } + + int aCount = a.getParameterCount(); + int bCount = b.getParameterCount(); + + if (bCount < aCount) { + return -1; + } else if (aCount < bCount) { + return 1; + } + + return 0; + } + + /** + * Return the traditional main method or null if not found. + * + * @param mainClass main class + * + * @return main method or null + */ + private static Method getTraditionalMain(Class mainClass) { + try { + Method traditionalMain = mainClass.getMethod("main", String[].class); + int mods = traditionalMain.getModifiers(); + + if (Modifier.isStatic(mods) && Modifier.isPublic(mods) && traditionalMain.getReturnType() == void.class) { + return traditionalMain; + } + } catch (NoSuchMethodException ex) { + // not found + } + + return null; + } + + /** + * {@return priority main method if none found} + * + * @param mainClass main class + * + * @throws NoSuchMethodException when not preview and no method found + */ + public static Method findMainMethod(Class mainClass) throws NoSuchMethodException { + boolean isTraditionMain = !PreviewFeatures.isEnabled(); + if (isTraditionMain) { + return mainClass.getMethod("main", String[].class); + } + + List mains = new ArrayList<>(); + gatherMains(mainClass, mains, true); + + if (mains.isEmpty()) { + throw new NoSuchMethodException("No main method found"); + } + + if (1 < mains.size()) { + mains.sort(MainMethodFinder::compareMethods); + } + + Method mainMethod = mains.get(0); + Method traditionalMain = getTraditionalMain(mainClass); + + if (traditionalMain != null && !traditionalMain.equals(mainMethod)) { + System.err.println("WARNING: \"" + mains.get(0) + "\" chosen over \"" + traditionalMain + "\""); + } + + return mains.get(0); + } +} diff --git a/src/java.base/share/classes/sun/launcher/LauncherHelper.java b/src/java.base/share/classes/sun/launcher/LauncherHelper.java index 5b083ec048e..abd0acbf75c 100644 --- a/src/java.base/share/classes/sun/launcher/LauncherHelper.java +++ b/src/java.base/share/classes/sun/launcher/LauncherHelper.java @@ -48,6 +48,7 @@ import java.lang.module.ModuleFinder; import java.lang.module.ModuleReference; import java.lang.module.ResolvedModule; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.math.BigDecimal; @@ -78,6 +79,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import jdk.internal.util.OperatingSystem; +import jdk.internal.misc.MainMethodFinder; +import jdk.internal.misc.PreviewFeatures; import jdk.internal.misc.VM; import jdk.internal.module.ModuleBootstrap; import jdk.internal.module.Modules; @@ -843,11 +846,32 @@ public final class LauncherHelper { return false; } + /* + * main type flags + */ + private static final int MAIN_WITHOUT_ARGS = 1; + private static final int MAIN_NONSTATIC = 2; + private static int mainType = 0; + + /* + * Return type so that launcher invokes the correct main + */ + public static int getMainType() { + return mainType; + } + + private static void setMainType(Method mainMethod) { + int mods = mainMethod.getModifiers(); + boolean isStatic = Modifier.isStatic(mods); + boolean noArgs = mainMethod.getParameterCount() == 0; + mainType = (isStatic ? 0 : MAIN_NONSTATIC) | (noArgs ? MAIN_WITHOUT_ARGS : 0); + } + // Check the existence and signature of main and abort if incorrect static void validateMainClass(Class mainClass) { Method mainMethod = null; try { - mainMethod = mainClass.getMethod("main", String[].class); + mainMethod = MainMethodFinder.findMainMethod(mainClass); } catch (NoSuchMethodException nsme) { // invalid main or not FX application, abort with an error abort(null, "java.launcher.cls.error4", mainClass.getName(), @@ -863,16 +887,42 @@ public final class LauncherHelper { } } + setMainType(mainMethod); + /* - * getMethod (above) will choose the correct method, based + * findMainMethod (above) will choose the correct method, based * on its name and parameter type, however, we still have to - * ensure that the method is static and returns a void. + * ensure that the method is static (non-preview) and returns a void. */ - int mod = mainMethod.getModifiers(); - if (!Modifier.isStatic(mod)) { - abort(null, "java.launcher.cls.error2", "static", - mainMethod.getDeclaringClass().getName()); + int mods = mainMethod.getModifiers(); + boolean isStatic = Modifier.isStatic(mods); + boolean isPublic = Modifier.isPublic(mods); + boolean noArgs = mainMethod.getParameterCount() == 0; + + if (!PreviewFeatures.isEnabled()) { + if (!isStatic || !isPublic || noArgs) { + abort(null, "java.launcher.cls.error2", "static", + mainMethod.getDeclaringClass().getName()); + } } + + if (!isStatic) { + if (mainClass.isMemberClass() && !Modifier.isStatic(mainClass.getModifiers())) { + abort(null, "java.launcher.cls.error9", + mainMethod.getDeclaringClass().getName()); + } + try { + Constructor constructor = mainClass.getDeclaredConstructor(); + if (Modifier.isPrivate(constructor.getModifiers())) { + abort(null, "java.launcher.cls.error8", + mainMethod.getDeclaringClass().getName()); + } + } catch (Throwable ex) { + abort(null, "java.launcher.cls.error8", + mainMethod.getDeclaringClass().getName()); + } + } + if (mainMethod.getReturnType() != java.lang.Void.TYPE) { abort(null, "java.launcher.cls.error3", mainMethod.getDeclaringClass().getName()); diff --git a/src/java.base/share/classes/sun/launcher/resources/launcher.properties b/src/java.base/share/classes/sun/launcher/resources/launcher.properties index de8cbaba197..c6aee63bf9c 100644 --- a/src/java.base/share/classes/sun/launcher/resources/launcher.properties +++ b/src/java.base/share/classes/sun/launcher/resources/launcher.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2007, 2021, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2007, 2023, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -236,6 +236,13 @@ java.launcher.cls.error6=\ java.launcher.cls.error7=\ Error: Unable to initialize main class {0}\n\ Caused by: {1}: {2} +java.launcher.cls.error8=\ + Error: no non-private zero argument constructor found in class {0}\n\ + remove private from existing constructor or define as:\n\ +\ public {0}() +java.launcher.cls.error9=\ + Error: non-static inner class {0} constructor can not be invoked \n\ + make inner class static or move inner class out to separate source file java.launcher.jar.error1=\ Error: An unexpected error occurred while trying to open file {0} java.launcher.jar.error2=manifest not found in {0} diff --git a/src/java.base/share/native/libjli/java.c b/src/java.base/share/native/libjli/java.c index 57d25df4267..3b4a15bac5b 100644 --- a/src/java.base/share/native/libjli/java.c +++ b/src/java.base/share/native/libjli/java.c @@ -401,8 +401,10 @@ JavaMain(void* _args) JNIEnv *env = 0; jclass mainClass = NULL; jclass appClass = NULL; // actual application class being launched - jmethodID mainID; jobjectArray mainArgs; + jmethodID mainID; + jmethodID constructor; + jobject mainObject; int ret = 0; jlong start = 0, end = 0; @@ -539,12 +541,55 @@ JavaMain(void* _args) * is not required. The main method is invoked here so that extraneous java * stacks are not in the application stack trace. */ - mainID = (*env)->GetStaticMethodID(env, mainClass, "main", - "([Ljava/lang/String;)V"); - CHECK_EXCEPTION_NULL_LEAVE(mainID); +#define MAIN_WITHOUT_ARGS 1 +#define MAIN_NONSTATIC 2 - /* Invoke main method. */ - (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs); + jclass helperClass = GetLauncherHelperClass(env); + jmethodID getMainType = (*env)->GetStaticMethodID(env, helperClass, + "getMainType", + "()I"); + CHECK_EXCEPTION_NULL_LEAVE(getMainType); + int mainType = (*env)->CallStaticIntMethod(env, helperClass, getMainType); + CHECK_EXCEPTION_LEAVE(mainType); + + switch (mainType) { + case 0: { + mainID = (*env)->GetStaticMethodID(env, mainClass, "main", + "([Ljava/lang/String;)V"); + CHECK_EXCEPTION_NULL_LEAVE(mainID); + (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs); + break; + } + case MAIN_WITHOUT_ARGS: { + mainID = (*env)->GetStaticMethodID(env, mainClass, "main", + "()V"); + CHECK_EXCEPTION_NULL_LEAVE(mainID); + (*env)->CallStaticVoidMethod(env, mainClass, mainID); + break; + } + case MAIN_NONSTATIC: { + constructor = (*env)->GetMethodID(env, mainClass, "", "()V"); + CHECK_EXCEPTION_NULL_LEAVE(constructor); + mainObject = (*env)->NewObject(env, mainClass, constructor); + CHECK_EXCEPTION_NULL_LEAVE(mainObject); + mainID = (*env)->GetMethodID(env, mainClass, "main", + "([Ljava/lang/String;)V"); + CHECK_EXCEPTION_NULL_LEAVE(mainID); + (*env)->CallVoidMethod(env, mainObject, mainID, mainArgs); + break; + } + case MAIN_NONSTATIC | MAIN_WITHOUT_ARGS: { + constructor = (*env)->GetMethodID(env, mainClass, "", "()V"); + CHECK_EXCEPTION_NULL_LEAVE(constructor); + mainObject = (*env)->NewObject(env, mainClass, constructor); + CHECK_EXCEPTION_NULL_LEAVE(mainObject); + mainID = (*env)->GetMethodID(env, mainClass, "main", + "()V"); + CHECK_EXCEPTION_NULL_LEAVE(mainID); + (*env)->CallVoidMethod(env, mainObject, mainID); + break; + } + } /* * The launcher's exit code (in the absence of calls to diff --git a/src/java.compiler/share/classes/javax/annotation/processing/Filer.java b/src/java.compiler/share/classes/javax/annotation/processing/Filer.java index a2e73ee8786..9ebcf2c5908 100644 --- a/src/java.compiler/share/classes/javax/annotation/processing/Filer.java +++ b/src/java.compiler/share/classes/javax/annotation/processing/Filer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,6 +28,7 @@ package javax.annotation.processing; import javax.tools.JavaFileManager; import javax.tools.*; import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; import javax.lang.model.util.Elements; import java.io.IOException; @@ -176,6 +177,13 @@ public interface Filer { *

Creating a source file in or for an unnamed package in a named * module is not supported. * + *

If the environment is configured to support {@linkplain + * TypeElement#isUnnamed unnamed classes}, the name argument is + * used to provide the leading component of the name used for the + * output file. For example {@code filer.createSourceFile("Foo")} + * to create an unnamed class hosted in {@code Foo.java}. All + * unnamed classes must be in an unnamed package. + * * @apiNote To use a particular {@linkplain * java.nio.charset.Charset charset} to encode the contents of the * file, an {@code OutputStreamWriter} with the chosen charset can @@ -255,6 +263,13 @@ public interface Filer { *

Creating a class file in or for an unnamed package in a named * module is not supported. * + *

If the environment is configured to support {@linkplain + * TypeElement#isUnnamed unnamed classes}, the name argument is + * used to provide the leading component of the name used for the + * output file. For example {@code filer.createClassFile("Foo")} to + * create an unnamed class hosted in {@code Foo.class}. All unnamed + * classes must be in an unnamed package. + * * @apiNote To avoid subsequent errors, the contents of the class * file should be compatible with the {@linkplain * ProcessingEnvironment#getSourceVersion source version} being diff --git a/src/java.compiler/share/classes/javax/lang/model/element/TypeElement.java b/src/java.compiler/share/classes/javax/lang/model/element/TypeElement.java index 68df8920342..48bc4132eea 100644 --- a/src/java.compiler/share/classes/javax/lang/model/element/TypeElement.java +++ b/src/java.compiler/share/classes/javax/lang/model/element/TypeElement.java @@ -25,6 +25,8 @@ package javax.lang.model.element; +import jdk.internal.javac.PreviewFeature; + import java.util.List; import javax.lang.model.type.*; import javax.lang.model.util.*; @@ -147,7 +149,7 @@ public interface TypeElement extends Element, Parameterizable, QualifiedNameable /** * Returns the fully qualified name of this class or interface * element. More precisely, it returns the canonical name. - * For local and anonymous classes, which do not have canonical + * For local, anonymous, and {@linkplain #isUnnamed() unnamed} classes, which do not have canonical * names, an {@linkplain Name##empty_name empty name} is * returned. * @@ -163,6 +165,7 @@ public interface TypeElement extends Element, Parameterizable, QualifiedNameable * * @see Elements#getBinaryName * @jls 6.7 Fully Qualified Names and Canonical Names + * @jls 7.3 Compilation Units */ Name getQualifiedName(); @@ -172,6 +175,10 @@ public interface TypeElement extends Element, Parameterizable, QualifiedNameable * For an anonymous class, an {@linkplain Name##empty_name empty * name} is returned. * + * For an {@linkplain #isUnnamed() unnamed} class, a name matching + * the base name of the hosting file, minus any extension, is + * returned. + * * @return the simple name of this class or interface, * an empty name for an anonymous class * @@ -179,6 +186,22 @@ public interface TypeElement extends Element, Parameterizable, QualifiedNameable @Override Name getSimpleName(); + /** + * {@return {@code true} if this is an unnamed class and {@code + * false} otherwise} + * + * @implSpec + * The default implementation of this method returns {@code false}. + * + * @jls 7.3 Compilation Units + * @since 21 + */ + @PreviewFeature(feature=PreviewFeature.Feature.UNNAMED_CLASSES, + reflective=true) + default boolean isUnnamed() { + return false; + } + /** * Returns the direct superclass of this class or interface element. * If this class or interface element represents an interface or the class diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/ClassFinder.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/ClassFinder.java index 770adbebc2e..56b692fb7cc 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/ClassFinder.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/ClassFinder.java @@ -444,6 +444,9 @@ public class ClassFinder { if (c.members_field == null) { try { c.complete(); + if ((c.flags_field & UNNAMED_CLASS) != 0) { + syms.removeClass(ps.modle, flatname); + } } catch (CompletionFailure ex) { if (absent) { syms.removeClass(ps.modle, flatname); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java index f87dcee526e..8799f315067 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -123,6 +123,10 @@ public class Flags { */ public static final int HASINIT = 1<<18; + /** Class is a unnamed top level class. + */ + public static final int UNNAMED_CLASS = 1<<19; + /** Flag is set for compiler-generated anonymous method symbols * that `own' an initializer block. */ @@ -490,6 +494,7 @@ public class Flags { ANNOTATION(Flags.ANNOTATION), DEPRECATED(Flags.DEPRECATED), HASINIT(Flags.HASINIT), + UNNAMED_CLASS(Flags.UNNAMED_CLASS), BLOCK(Flags.BLOCK), FROM_SOURCE(Flags.FROM_SOURCE), ENUM(Flags.ENUM), diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Preview.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Preview.java index 6a7b0d22578..bb1c736904f 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Preview.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Preview.java @@ -210,6 +210,7 @@ public class Preview { public boolean isPreview(Feature feature) { return switch (feature) { case STRING_TEMPLATES -> true; + case UNNAMED_CLASSES -> true; case UNNAMED_VARIABLES -> true; //Note: this is a backdoor which allows to optionally treat all features as 'preview' (for testing). //When real preview features will be added, this method can be implemented to return 'true' diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java index 293de05d48f..95f81453c61 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -238,6 +238,7 @@ public enum Source { UNCONDITIONAL_PATTERN_IN_INSTANCEOF(JDK21, Fragments.FeatureUnconditionalPatternsInInstanceof, DiagKind.PLURAL), RECORD_PATTERNS(JDK21, Fragments.FeatureDeconstructionPatterns, DiagKind.PLURAL), STRING_TEMPLATES(JDK21, Fragments.FeatureStringTemplates, DiagKind.PLURAL), + UNNAMED_CLASSES(JDK21, Fragments.FeatureUnnamedClasses, DiagKind.PLURAL), WARN_ON_ILLEGAL_UTF8(MIN, JDK21), UNNAMED_VARIABLES(JDK21, Fragments.FeatureUnnamedVariables, DiagKind.PLURAL), ; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java index 71dcc94f339..9e08a8cdd83 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java @@ -1256,6 +1256,7 @@ public abstract class Symbol extends AnnoConstruct implements PoolConstant, Elem /** A class for class symbols */ + @SuppressWarnings("preview") // isUnnamed() public static class ClassSymbol extends TypeSymbol implements TypeElement { /** a scope for all class members; variables, methods and inner classes @@ -1367,10 +1368,15 @@ public abstract class Symbol extends AnnoConstruct implements PoolConstant, Elem return fullname.toString(); } - @DefinedBy(Api.LANGUAGE_MODEL) - public Name getQualifiedName() { - return fullname; - } + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public Name getQualifiedName() { + return isUnnamed() ? fullname.subName(0, 0) /* empty name */ : fullname; + } + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public Name getSimpleName() { + return name; + } @Override @DefinedBy(Api.LANGUAGE_MODEL) public List getEnclosedElements() { @@ -1545,7 +1551,7 @@ public abstract class Symbol extends AnnoConstruct implements PoolConstant, Elem @DefinedBy(Api.LANGUAGE_MODEL) public NestingKind getNestingKind() { apiComplete(); - if (owner.kind == PCK) + if (owner.kind == PCK) // Handles unnamed classes as well return NestingKind.TOP_LEVEL; else if (name.isEmpty()) return NestingKind.ANONYMOUS; @@ -1636,6 +1642,11 @@ public abstract class Symbol extends AnnoConstruct implements PoolConstant, Elem public List getPermittedSubclasses() { return permitted.map(s -> s.type); } + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public boolean isUnnamed() { + return (flags_field & Flags.UNNAMED_CLASS) != 0 ; + } } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java index b6ceb2a33af..fc646cc746e 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java @@ -5511,6 +5511,10 @@ public class Attr extends JCTree.Visitor { chk.checkClassOverrideEqualsAndHashIfNeeded(env.tree.pos(), c); chk.checkFunctionalInterface((JCClassDecl) env.tree, c); chk.checkLeaksNotAccessible(env, (JCClassDecl) env.tree); + + if ((c.flags_field & Flags.UNNAMED_CLASS) != 0) { + chk.checkHasMain(env.tree.pos(), c); + } } finally { env.info.returnResult = prevReturnRes; log.useSource(prev); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java index c767168b13a..b1f34f511ab 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -2250,6 +2250,36 @@ public class Check { } } + public void checkHasMain(DiagnosticPosition pos, ClassSymbol c) { + boolean found = false; + + for (Symbol sym : c.members().getSymbolsByName(names.main)) { + if (sym.kind == MTH && (sym.flags() & PRIVATE) == 0) { + MethodSymbol meth = (MethodSymbol)sym; + if (!types.isSameType(meth.getReturnType(), syms.voidType)) { + continue; + } + if (meth.params.isEmpty()) { + found = true; + break; + } + if (meth.params.size() != 1) { + continue; + } + if (!types.isSameType(meth.params.head.type, types.makeArrayType(syms.stringType))) { + continue; + } + + found = true; + break; + } + } + + if (!found) { + log.error(pos, Errors.UnnamedClassDoesNotHaveMainMethod); + } + } + public void checkModuleName (JCModuleDecl tree) { Name moduleName = tree.sym.name; Assert.checkNonNull(moduleName); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Enter.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Enter.java index 808d4bb294f..a50788ed4b8 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Enter.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Enter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -308,8 +308,6 @@ public class Enter extends JCTree.Visitor { @Override public void visitTopLevel(JCCompilationUnit tree) { -// Assert.checkNonNull(tree.modle, tree.sourcefile.toString()); - JavaFileObject prev = log.useSource(tree.sourcefile); boolean addEnv = false; boolean isPkgInfo = tree.sourcefile.isNameCompatible("package-info", @@ -441,6 +439,9 @@ public class Enter extends JCTree.Visitor { log.error(tree.pos(), Errors.ClassPublicShouldBeInFile(topElement, tree.name)); } + if ((tree.mods.flags & UNNAMED_CLASS) != 0) { + syms.removeClass(env.toplevel.modle, tree.name); + } } else { if (!tree.name.isEmpty() && !chk.checkUniqueClassName(tree.pos(), tree.name, enclScope)) { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java index d521951041b..295bb192a42 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java @@ -53,7 +53,6 @@ import com.sun.tools.javac.util.DefinedBy.Api; import com.sun.tools.javac.util.JCDiagnostic.DiagnosticFlag; import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; import com.sun.tools.javac.util.JCDiagnostic.DiagnosticType; -import com.sun.tools.javac.util.JCDiagnostic.Warning; import java.util.Arrays; import java.util.Collection; @@ -65,7 +64,6 @@ import java.util.Map; import java.util.Set; import java.util.function.BiFunction; import java.util.function.BiPredicate; -import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.UnaryOperator; @@ -2432,7 +2430,6 @@ public class Resolve { if (kind.contains(KindSelector.TYP)) { sym = findType(env, name); - if (sym.exists()) return sym; else bestSoFar = bestOf(bestSoFar, sym); } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassReader.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassReader.java index 1fda5ed74fc..f13900f1f55 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassReader.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassReader.java @@ -2730,6 +2730,11 @@ public class ClassReader { signatureBuffer = new byte[ns]; } readClass(c); + if (previewClassFile) { + if ((c.flags_field & SYNTHETIC) != 0 && c.isSubClass(syms.objectType.tsym, types)) { + c.flags_field |= UNNAMED_CLASS; + } + } } public void readClassFile(ClassSymbol c) { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/Main.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/Main.java index 8d907dba293..729d1cf2b05 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/Main.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/Main.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -38,6 +38,7 @@ import java.io.OutputStreamWriter; import java.io.PrintStream; import java.io.PrintWriter; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.MalformedURLException; @@ -84,6 +85,8 @@ import com.sun.tools.javac.code.Source; import com.sun.tools.javac.resources.LauncherProperties.Errors; import com.sun.tools.javac.util.JCDiagnostic.Error; +import jdk.internal.misc.MainMethodFinder; +import jdk.internal.misc.PreviewFeatures; import jdk.internal.misc.VM; import static javax.tools.JavaFileObject.Kind.SOURCE; @@ -201,8 +204,8 @@ public class Main { Context context = new Context(file.toAbsolutePath()); String mainClassName = compile(file, getJavacOpts(runtimeArgs), context); - String[] appArgs = Arrays.copyOfRange(args, 1, args.length); - execute(mainClassName, appArgs, context); + String[] mainArgs = Arrays.copyOfRange(args, 1, args.length); + execute(mainClassName, mainArgs, context); } /** @@ -403,7 +406,9 @@ public class Main { if (l.mainClass == null) { throw new Fault(Errors.NoClass); } - String mainClassName = l.mainClass.getQualifiedName().toString(); + TypeElement mainClass = l.mainClass; + String mainClassName = (mainClass.isUnnamed() ? mainClass.getSimpleName() + : mainClass.getQualifiedName()).toString(); return mainClassName; } @@ -412,31 +417,72 @@ public class Main { * will load recently compiled classes from memory. * * @param mainClassName the class to be executed - * @param appArgs the arguments for the {@code main} method + * @param mainArgs the arguments for the {@code main} method * @param context the context for the class to be executed * @throws Fault if there is a problem finding or invoking the {@code main} method * @throws InvocationTargetException if the {@code main} method throws an exception */ - private void execute(String mainClassName, String[] appArgs, Context context) + private void execute(String mainClassName, String[] mainArgs, Context context) throws Fault, InvocationTargetException { System.setProperty("jdk.launcher.sourcefile", context.file.toString()); ClassLoader cl = context.getClassLoader(ClassLoader.getSystemClassLoader()); + + Class appClass; try { - Class appClass = Class.forName(mainClassName, true, cl); - Method main = appClass.getDeclaredMethod("main", String[].class); - int PUBLIC_STATIC = Modifier.PUBLIC | Modifier.STATIC; - if ((main.getModifiers() & PUBLIC_STATIC) != PUBLIC_STATIC) { - throw new Fault(Errors.MainNotPublicStatic); - } - if (!main.getReturnType().equals(void.class)) { - throw new Fault(Errors.MainNotVoid); - } - main.setAccessible(true); - main.invoke(0, (Object) appArgs); + appClass = Class.forName(mainClassName, true, cl); } catch (ClassNotFoundException e) { throw new Fault(Errors.CantFindClass(mainClassName)); + } + + Method mainMethod; + try { + mainMethod = MainMethodFinder.findMainMethod(appClass); } catch (NoSuchMethodException e) { throw new Fault(Errors.CantFindMainMethod(mainClassName)); + } + + int mods = mainMethod.getModifiers(); + boolean isStatic = Modifier.isStatic(mods); + boolean isPublic = Modifier.isPublic(mods); + boolean noArgs = mainMethod.getParameterCount() == 0; + + if (!PreviewFeatures.isEnabled() && (!isStatic || !isPublic)) { + throw new Fault(Errors.MainNotPublicStatic); + } + + if (!mainMethod.getReturnType().equals(void.class)) { + throw new Fault(Errors.MainNotVoid); + } + + Object instance = null; + + if (!isStatic) { + Constructor constructor; + try { + constructor = appClass.getDeclaredConstructor(); + } catch (NoSuchMethodException e) { + throw new Fault(Errors.CantFindConstructor(mainClassName)); + } + + try { + constructor.setAccessible(true); + instance = constructor.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new Fault(Errors.CantAccessConstructor(mainClassName)); + } + } + + try { + // Similar to sun.launcher.LauncherHelper#executeMainClass + // but duplicated here to prevent additional launcher frames + mainMethod.setAccessible(true); + Object receiver = isStatic ? appClass : instance; + + if (noArgs) { + mainMethod.invoke(receiver); + } else { + mainMethod.invoke(receiver, (Object)mainArgs); + } } catch (IllegalAccessException e) { throw new Fault(Errors.CantAccessMainMethod(mainClassName)); } catch (InvocationTargetException e) { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java index 7a8dcaef39c..0e408bea7ca 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java @@ -30,12 +30,15 @@ import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; +import javax.lang.model.SourceVersion; + import com.sun.source.tree.CaseTree; import com.sun.source.tree.MemberReferenceTree.ReferenceMode; import com.sun.source.tree.ModuleTree.ModuleKind; import com.sun.tools.javac.code.*; import com.sun.tools.javac.code.Source.Feature; +import com.sun.tools.javac.file.PathFileObject; import com.sun.tools.javac.parser.Tokens.*; import com.sun.tools.javac.parser.Tokens.Comment.CommentStyle; import com.sun.tools.javac.resources.CompilerProperties.Errors; @@ -182,10 +185,33 @@ public class JavacParser implements Parser { this.allowStringFolding = fac.options.getBoolean("allowStringFolding", true); this.keepDocComments = keepDocComments; this.parseModuleInfo = parseModuleInfo; - docComments = newDocCommentTable(keepDocComments, fac); + this.docComments = newDocCommentTable(keepDocComments, fac); this.keepLineMap = keepLineMap; this.errorTree = F.Erroneous(); - endPosTable = newEndPosTable(keepEndPositions); + this.endPosTable = newEndPosTable(keepEndPositions); + this.allowYieldStatement = Feature.SWITCH_EXPRESSION.allowedInSource(source); + this.allowRecords = Feature.RECORDS.allowedInSource(source); + this.allowSealedTypes = Feature.SEALED_CLASSES.allowedInSource(source); + } + + /** Construct a parser from an existing parser, with minimal overhead. + */ + @SuppressWarnings("this-escape") + protected JavacParser(JavacParser parser, + Lexer S) { + this.S = S; + this.token = parser.token; + this.F = parser.F; + this.log = parser.log; + this.names = parser.names; + this.source = parser.source; + this.preview = parser.preview; + this.allowStringFolding = parser.allowStringFolding; + this.keepDocComments = false; + this.parseModuleInfo = false; + this.docComments = null; + this.errorTree = F.Erroneous(); + this.endPosTable = newEndPosTable(false); this.allowYieldStatement = Feature.SWITCH_EXPRESSION.allowedInSource(source); this.allowRecords = Feature.RECORDS.allowedInSource(source); this.allowSealedTypes = Feature.SEALED_CLASSES.allowedInSource(source); @@ -2794,10 +2820,7 @@ public class JavacParser implements Parser { case FINAL: { dc = token.comment(CommentStyle.JAVADOC); JCModifiers mods = modifiersOpt(); - if (token.kind == INTERFACE || - token.kind == CLASS || - token.kind == ENUM || - isRecordStart()) { + if (isDeclaration()) { return List.of(classOrRecordOrInterfaceOrEnumDeclaration(mods, dc)); } else { JCExpression t = parseType(true); @@ -3891,6 +3914,7 @@ public class JavacParser implements Parser { } boolean firstTypeDecl = true; // have we see a class, enum, or interface declaration yet? + boolean isUnnamedClass = false; while (token.kind != EOF) { if (token.pos <= endPosTable.errorEndPos) { // error recovery @@ -3949,16 +3973,43 @@ public class JavacParser implements Parser { reportSyntaxError(token.pos, Errors.ExpectedModule); } } + defs.appendList(semiList.toList()); - JCTree def = typeDeclaration(mods, docComment); - if (def instanceof JCExpressionStatement statement) - def = statement.expr; - defs.append(def); + boolean isTopLevelMethodOrField = false; + + // Do to a significant number of existing negative tests + // this code speculatively tests to see if a top level method + // or field can parse. If the method or field can parse then + // it is parsed. Otherwise, parsing continues as though + // unnamed classes did not exist and error reporting + // is the same as in the past. + if (Feature.UNNAMED_CLASSES.allowedInSource(source) && !isDeclaration()) { + final JCModifiers finalMods = mods; + JavacParser speculative = new VirtualParser(this); + List speculativeResult = + speculative.topLevelMethodOrFieldDeclaration(finalMods); + if (speculativeResult.head.hasTag(METHODDEF) || + speculativeResult.head.hasTag(VARDEF)) { + isTopLevelMethodOrField = true; + } + } + + if (isTopLevelMethodOrField) { + defs.appendList(topLevelMethodOrFieldDeclaration(mods)); + isUnnamedClass = true; + } else { + JCTree def = typeDeclaration(mods, docComment); + if (def instanceof JCExpressionStatement statement) + def = statement.expr; + defs.append(def); + } + mods = null; firstTypeDecl = false; } } - JCTree.JCCompilationUnit toplevel = F.at(firstToken.pos).TopLevel(defs.toList()); + List topLevelDefs = isUnnamedClass ? constructUnnamedClass(defs.toList()) : defs.toList(); + JCTree.JCCompilationUnit toplevel = F.at(firstToken.pos).TopLevel(topLevelDefs); if (!consumedToplevelDoc) attach(toplevel, firstToken.comment(CommentStyle.JAVADOC)); if (defs.isEmpty()) @@ -3972,6 +4023,43 @@ public class JavacParser implements Parser { return toplevel; } + // Restructure top level to be an unnamed class. + private List constructUnnamedClass(List origDefs) { + checkSourceLevel(Feature.UNNAMED_CLASSES); + + ListBuffer topDefs = new ListBuffer<>(); + ListBuffer defs = new ListBuffer<>(); + + for (JCTree def : origDefs) { + if (def.hasTag(Tag.PACKAGEDEF)) { + log.error(def.pos(), Errors.UnnamedClassShouldNotHavePackageDeclaration); + } else if (def.hasTag(Tag.IMPORT)) { + topDefs.append(def); + } else if (!def.hasTag(Tag.SKIP)) { + defs.append(def); + } + } + + int primaryPos = defs.first().pos; + String simplename = PathFileObject.getSimpleName(log.currentSourceFile()); + + if (simplename.endsWith(".java")) { + simplename = simplename.substring(0, simplename.length() - ".java".length()); + } + if (!SourceVersion.isIdentifier(simplename) || SourceVersion.isKeyword(simplename)) { + log.error(primaryPos, Errors.BadFileName(simplename)); + } + + Name name = names.fromString(simplename); + JCModifiers anonMods = F.at(primaryPos) + .Modifiers(Flags.FINAL|Flags.SYNTHETIC|Flags.UNNAMED_CLASS, List.nil()); + JCClassDecl anon = F.at(primaryPos).ClassDef( + anonMods, name, List.nil(), null, List.nil(), List.nil(), + defs.toList()); + topDefs.append(anon); + return topDefs.toList(); + } + JCModuleDecl moduleDecl(JCModifiers mods, ModuleKind kind, Comment dc) { int pos = token.pos; checkSourceLevel(Feature.MODULES); @@ -4132,17 +4220,17 @@ public class JavacParser implements Parser { } else { errs = List.of(mods); } - final JCErroneous erroneousTree; + + JCDiagnostic.Error error; if (parseModuleInfo) { - erroneousTree = syntaxError(pos, errs, Errors.ExpectedModuleOrOpen); + error = Errors.ExpectedModuleOrOpen; + } else if (allowRecords) { + error = Errors.Expected4(CLASS, INTERFACE, ENUM, "record"); } else { - if (allowRecords) { - erroneousTree = syntaxError(pos, errs, Errors.Expected4(CLASS, INTERFACE, ENUM, "record")); - } else { - erroneousTree = syntaxError(pos, errs, Errors.Expected3(CLASS, INTERFACE, ENUM)); - } + error = Errors.Expected3(CLASS, INTERFACE, ENUM); } - return toP(F.Exec(erroneousTree)); + return toP(F.Exec(syntaxError(pos, errs, error))); + } } @@ -4363,7 +4451,7 @@ public class JavacParser implements Parser { hasStructuralErrors = true; } wasError = false; - defs.appendList(classOrInterfaceOrRecordBodyDeclaration(enumName, + defs.appendList(classOrInterfaceOrRecordBodyDeclaration(null, enumName, false, false)); if (token.pos <= endPosTable.errorEndPos) { // error recovery @@ -4469,11 +4557,11 @@ public class JavacParser implements Parser { } ListBuffer defs = new ListBuffer<>(); while (token.kind != RBRACE && token.kind != EOF) { - defs.appendList(classOrInterfaceOrRecordBodyDeclaration(className, isInterface, isRecord)); + defs.appendList(classOrInterfaceOrRecordBodyDeclaration(null, className, isInterface, isRecord)); if (token.pos <= endPosTable.errorEndPos) { // error recovery skip(false, true, true, false); - } + } } accept(RBRACE); return defs.toList(); @@ -4508,18 +4596,17 @@ public class JavacParser implements Parser { * ) * */ - protected List classOrInterfaceOrRecordBodyDeclaration(Name className, boolean isInterface, boolean isRecord) { + protected List classOrInterfaceOrRecordBodyDeclaration(JCModifiers mods, Name className, + boolean isInterface, + boolean isRecord) { if (token.kind == SEMI) { nextToken(); return List.nil(); } else { Comment dc = token.comment(CommentStyle.JAVADOC); int pos = token.pos; - JCModifiers mods = modifiersOpt(); - if (token.kind == CLASS || - allowRecords && isRecordStart() || - token.kind == INTERFACE || - token.kind == ENUM) { + mods = modifiersOpt(mods); + if (isDeclaration()) { return List.of(classOrRecordOrInterfaceOrEnumDeclaration(mods, dc)); } else if (token.kind == LBRACE && (mods.flags & Flags.StandardFlags & ~Flags.STATIC) == 0 && @@ -4531,97 +4618,185 @@ public class JavacParser implements Parser { } return List.of(block(pos, mods.flags)); } else { - pos = token.pos; - List typarams = typeParametersOpt(); - // if there are type parameters but no modifiers, save the start - // position of the method in the modifiers. - if (typarams.nonEmpty() && mods.pos == Position.NOPOS) { - mods.pos = pos; - storeEnd(mods, pos); - } - List annosAfterParams = annotationsOpt(Tag.ANNOTATION); - - if (annosAfterParams.nonEmpty()) { - mods.annotations = mods.annotations.appendList(annosAfterParams); - if (mods.pos == Position.NOPOS) - mods.pos = mods.annotations.head.pos; - } - - Token tk = token; - pos = token.pos; - JCExpression type; - boolean isVoid = token.kind == VOID; - if (isVoid) { - type = to(F.at(pos).TypeIdent(TypeTag.VOID)); - nextToken(); - } else { - // method returns types are un-annotated types - type = unannotatedType(false); - } - if ((token.kind == LPAREN && !isInterface || - isRecord && token.kind == LBRACE) && type.hasTag(IDENT)) { - if (isInterface || tk.name() != className) - log.error(DiagnosticFlag.SYNTAX, pos, Errors.InvalidMethDeclRetTypeReq); - else if (annosAfterParams.nonEmpty()) - illegal(annosAfterParams.head.pos); - if (isRecord && token.kind == LBRACE) { - mods.flags |= Flags.COMPACT_RECORD_CONSTRUCTOR; - } - return List.of(methodDeclaratorRest( - pos, mods, null, names.init, typarams, - isInterface, true, isRecord, dc)); - } else if (isRecord && type.hasTag(IDENT) && token.kind == THROWS) { - // trying to define a compact constructor with a throws clause - log.error(DiagnosticFlag.SYNTAX, token.pos, - Errors.InvalidCanonicalConstructorInRecord( - Fragments.Compact, - className, - Fragments.ThrowsClauseNotAllowedForCanonicalConstructor(Fragments.Compact))); - skip(false, true, false, false); - return List.of(methodDeclaratorRest( - pos, mods, null, names.init, typarams, - isInterface, true, isRecord, dc)); - } else { - pos = token.pos; - Name name = ident(); - if (token.kind == LPAREN) { - return List.of(methodDeclaratorRest( - pos, mods, type, name, typarams, - isInterface, isVoid, false, dc)); - } else if (!isVoid && typarams.isEmpty()) { - if (!isRecord || (isRecord && (mods.flags & Flags.STATIC) != 0)) { - List defs = - variableDeclaratorsRest(pos, mods, type, name, isInterface, dc, - new ListBuffer(), false).toList(); - accept(SEMI); - storeEnd(defs.last(), S.prevToken().endPos); - return defs; - } else { - int errPos = pos; - variableDeclaratorsRest(pos, mods, type, name, isInterface, dc, - new ListBuffer(), false).toList(); - accept(SEMI); - return List.of(syntaxError(errPos, null, Errors.RecordCannotDeclareInstanceFields)); - } - } else { - pos = token.pos; - List err; - if (isVoid || typarams.nonEmpty()) { - JCMethodDecl m = - toP(F.at(pos).MethodDef(mods, name, type, typarams, - List.nil(), List.nil(), null, null)); - attach(m, dc); - err = List.of(m); - } else { - err = List.nil(); - } - return List.of(syntaxError(token.pos, err, Errors.Expected(LPAREN))); - } - } + return constructorOrMethodOrFieldDeclaration(mods, className, isInterface, isRecord, dc); } } } + private List constructorOrMethodOrFieldDeclaration(JCModifiers mods, Name className, + boolean isInterface, + boolean isRecord, Comment dc) { + int pos; + pos = token.pos; + List typarams = typeParametersOpt(); + // if there are type parameters but no modifiers, save the start + // position of the method in the modifiers. + if (typarams.nonEmpty() && mods.pos == Position.NOPOS) { + mods.pos = pos; + storeEnd(mods, pos); + } + List annosAfterParams = annotationsOpt(Tag.ANNOTATION); + + if (annosAfterParams.nonEmpty()) { + mods.annotations = mods.annotations.appendList(annosAfterParams); + if (mods.pos == Position.NOPOS) + mods.pos = mods.annotations.head.pos; + } + + Token tk = token; + pos = token.pos; + JCExpression type; + boolean isVoid = token.kind == VOID; + + if (isVoid) { + type = to(F.at(pos).TypeIdent(TypeTag.VOID)); + nextToken(); + } else { + // method returns types are un-annotated types + type = unannotatedType(false); + } + + // Constructor + if ((token.kind == LPAREN && !isInterface || + isRecord && token.kind == LBRACE) && type.hasTag(IDENT)) { + if (isInterface || tk.name() != className) { + log.error(DiagnosticFlag.SYNTAX, pos, Errors.InvalidMethDeclRetTypeReq); + } else if (annosAfterParams.nonEmpty()) { + illegal(annosAfterParams.head.pos); + } + + if (isRecord && token.kind == LBRACE) { + mods.flags |= Flags.COMPACT_RECORD_CONSTRUCTOR; + } + + return List.of(methodDeclaratorRest( + pos, mods, null, names.init, typarams, + isInterface, true, isRecord, dc)); + } + + // Record constructor + if (isRecord && type.hasTag(IDENT) && token.kind == THROWS) { + // trying to define a compact constructor with a throws clause + log.error(DiagnosticFlag.SYNTAX, token.pos, + Errors.InvalidCanonicalConstructorInRecord( + Fragments.Compact, + className, + Fragments.ThrowsClauseNotAllowedForCanonicalConstructor(Fragments.Compact))); + skip(false, true, false, false); + return List.of(methodDeclaratorRest( + pos, mods, null, names.init, typarams, + isInterface, true, isRecord, dc)); + } + + pos = token.pos; + Name name = ident(); + + // Method + if (token.kind == LPAREN) { + return List.of(methodDeclaratorRest( + pos, mods, type, name, typarams, + isInterface, isVoid, false, dc)); + } + + // Field + if (!isVoid && typarams.isEmpty()) { + if (!isRecord || (isRecord && (mods.flags & Flags.STATIC) != 0)) { + List defs = + variableDeclaratorsRest(pos, mods, type, name, isInterface, dc, + new ListBuffer(), false).toList(); + accept(SEMI); + storeEnd(defs.last(), S.prevToken().endPos); + return defs; + } + + int errPos = pos; + variableDeclaratorsRest(pos, mods, type, name, isInterface, dc, + new ListBuffer(), false).toList(); + accept(SEMI); + return List.of(syntaxError(errPos, null, Errors.RecordCannotDeclareInstanceFields)); + } + + pos = token.pos; + List err; + + // Error recovery + if (isVoid || typarams.nonEmpty()) { + JCMethodDecl m = + toP(F.at(pos).MethodDef(mods, name, type, typarams, + List.nil(), List.nil(), null, null)); + attach(m, dc); + err = List.of(m); + } else { + err = List.nil(); + } + + return List.of(syntaxError(token.pos, err, Errors.Expected(LPAREN))); + } + + private List topLevelMethodOrFieldDeclaration(JCModifiers mods) throws AssertionError { + int topPos = token.pos; + int pos = token.pos; + Comment dc = token.comment(CommentStyle.JAVADOC); + List typarams = typeParametersOpt(); + + // if there are type parameters but no modifiers, save the start + // position of the method in the modifiers. + if (typarams.nonEmpty() && mods.pos == Position.NOPOS) { + mods.pos = pos; + storeEnd(mods, pos); + } + + List annosAfterParams = annotationsOpt(Tag.ANNOTATION); + + if (annosAfterParams.nonEmpty()) { + mods.annotations = mods.annotations.appendList(annosAfterParams); + if (mods.pos == Position.NOPOS) + mods.pos = mods.annotations.head.pos; + } + + pos = token.pos; + JCExpression type; + boolean isVoid = token.kind == VOID; + + if (isVoid) { + type = to(F.at(pos).TypeIdent(TypeTag.VOID)); + nextToken(); + } else { + type = unannotatedType(false); + } + + if (token.kind == IDENTIFIER) { + pos = token.pos; + Name name = ident(); + + // Method + if (token.kind == LPAREN) { + return List.of(methodDeclaratorRest(pos, mods, type, name, typarams, + false, isVoid, false, dc)); + } + + // Field + if (!isVoid && typarams.isEmpty() && (token.kind == EQ || token.kind == SEMI)) { + List defs = + variableDeclaratorsRest(pos, mods, type, name, false, dc, + new ListBuffer(), false).toList(); + accept(SEMI); + storeEnd(defs.last(), S.prevToken().endPos); + + return defs; + } + } + + return List.of(F.Erroneous()); + } + + protected boolean isDeclaration() { + return token.kind == CLASS || + token.kind == INTERFACE || + token.kind == ENUM || + isRecordStart() && allowRecords; + } + protected boolean isRecordStart() { if (token.kind == IDENTIFIER && token.name() == names.record && peekToken(TokenKind.IDENTIFIER)) { checkSourceLevel(Feature.RECORDS); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/Scanner.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/Scanner.java index 81fa103a7e7..3cfb8b9ddbe 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/Scanner.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/Scanner.java @@ -42,7 +42,7 @@ import static com.sun.tools.javac.parser.Tokens.*; */ public class Scanner implements Lexer { - private final Tokens tokens; + protected Tokens tokens; /** The token, set by nextToken(). */ diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/VirtualParser.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/VirtualParser.java new file mode 100644 index 00000000000..6ad0a9353b9 --- /dev/null +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/VirtualParser.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.parser; + +import com.sun.tools.javac.parser.Tokens.Token; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCErroneous; +import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; +import com.sun.tools.javac.util.JCDiagnostic.Error; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.Position.LineMap; + +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * The virtual parser allows for speculative parsing while not commiting to + * consuming tokens unless the speculation is successful. + * + *

This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class VirtualParser extends JavacParser { + + private boolean hasErrors; + + public VirtualParser(JavacParser parser) { + super(parser, new VirtualScanner(parser.S)); + } + + @Override + protected JCErroneous syntaxError(int pos, Error errorKey) { + hasErrors = true; + return F.Erroneous(); + } + + @Override + protected JCErroneous syntaxError(int pos, List errs, Error errorKey) { + hasErrors = true; + return F.Erroneous(); + } + + @Override + protected void reportSyntaxError(int pos, Error errorKey) { + hasErrors = true; + } + + @Override + protected void reportSyntaxError(DiagnosticPosition diagPos, Error errorKey) { + hasErrors = true; + } + + public boolean hasErrors() { + return hasErrors; + } + + /** + * Scanner that does token lookahead and throws AssertionErrors if an error + * occurs. + */ + public static class VirtualScanner implements Lexer { + /** Parent scanner. + */ + Lexer S; + + /** Token offset from where parent scanner branched. + */ + int offset = 0; + + /** The token, set by nextToken(). + */ + private Token token; + + /** The previous token, set by nextToken(). + */ + private Token prevToken; + + public VirtualScanner(Lexer s) { + while (s instanceof VirtualScanner virtualScanner) { + s = virtualScanner.S; + offset += virtualScanner.offset; + } + S = s; + token = s.token(); + prevToken = S.prevToken(); + } + + @Override + public void nextToken() { + prevToken = token; + offset++; + token = token(); + } + + @Override + public Token token() { + return token(0); + } + + @Override + public Token token(int lookahead) { + return S.token(offset + lookahead); + } + + @Override + public Token prevToken() { + return prevToken; + } + + @Override + public void setPrevToken(Token prevToken) { + this.prevToken = prevToken; + } + + @Override + public Token split() { + Token[] splitTokens = token.split(((Scanner)S).tokens); + prevToken = splitTokens[0]; + token = splitTokens[1]; + return token; + } + + @Override + public int errPos() { + throw new AssertionError(); + } + + @Override + public void errPos(int pos) { + throw new AssertionError(); + } + + @Override + public LineMap getLineMap() { + return S.getLineMap(); + } + + public void commit() { + for (int i = 0 ; i < offset ; i++) { + S.nextToken(); // advance underlying lexer until position matches + } + } + } + + /** + * Attempts a parse action and returns true if successful or false if + * a parse error is thrown. + * + * @param parser parent parser + * @param parserAction function that takes a parser and invokes a method on that parser + * + * @return true if successful + */ + public static boolean tryParse(JavacParser parser, Consumer parserAction) { + VirtualParser virtualParser = new VirtualParser(parser); + try { + parserAction.accept(virtualParser); + return true; + } catch (AssertionError ex) { + return false; + } + } +} diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/PrintingProcessor.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/PrintingProcessor.java index babe341701a..9bcf098b08b 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/PrintingProcessor.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/PrintingProcessor.java @@ -118,6 +118,7 @@ public class PrintingProcessor extends AbstractProcessor { } @Override @DefinedBy(Api.LANGUAGE_MODEL) + @SuppressWarnings("preview") // isUnnamed public PrintingElementVisitor visitExecutable(ExecutableElement e, Boolean p) { ElementKind kind = e.getKind(); @@ -125,18 +126,26 @@ public class PrintingProcessor extends AbstractProcessor { kind != INSTANCE_INIT) { Element enclosing = e.getEnclosingElement(); - // Don't print out the constructor of an anonymous class + // Don't print out the constructor of an anonymous or unnamed class if (kind == CONSTRUCTOR && enclosing != null && - NestingKind.ANONYMOUS == + (NestingKind.ANONYMOUS == // Use an anonymous class to determine anonymity! (new SimpleElementVisitor14() { @Override @DefinedBy(Api.LANGUAGE_MODEL) public NestingKind visitType(TypeElement e, Void p) { return e.getNestingKind(); } - }).visit(enclosing)) + }).visit(enclosing) + || // Don't print the constructor of an unnamed class + (new SimpleElementVisitor14(false) { + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public Boolean visitType(TypeElement e, Void p) { + return e.isUnnamed(); + } + }).visit(enclosing)) ) { return this; + } defaultAction(e, true); printFormalTypeParameters(e, true); @@ -169,6 +178,7 @@ public class PrintingProcessor extends AbstractProcessor { @Override @DefinedBy(Api.LANGUAGE_MODEL) + @SuppressWarnings("preview") // isUnnamed public PrintingElementVisitor visitType(TypeElement e, Boolean p) { ElementKind kind = e.getKind(); NestingKind nestingKind = e.getNestingKind(); @@ -202,6 +212,14 @@ public class PrintingProcessor extends AbstractProcessor { printParameters(constructors.get(0)); } writer.print(")"); + } else if (e.isUnnamed()) { + writer.println("// Unnamed class in file whose name starts with " + e.getSimpleName()); + + for(Element element : e.getEnclosedElements()) { + this.visit(element); + } + + return this; } else { if (nestingKind == TOP_LEVEL) { PackageElement pkg = elementUtils.getPackageOf(e); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties index 72c64546126..30a4fb3b815 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -512,6 +512,16 @@ compiler.err.invalid.repeatable.annotation.not.applicable.in.context=\ compiler.err.duplicate.class=\ duplicate class: {0} +# 0: string +compiler.err.bad.file.name=\ + bad file name: {0} + +compiler.err.unnamed.class.should.not.have.package.declaration=\ + unnamed class should not have package declaration + +compiler.err.unnamed.class.does.not.have.main.method=\ + unnamed class does not have main method in the form of void main() or void main(String[] args) + # 0: name, 1: name compiler.err.same.binary.name=\ classes: {0} and {1} have the same binary name @@ -3158,6 +3168,9 @@ compiler.misc.feature.string.templates=\ compiler.misc.feature.unconditional.patterns.in.instanceof=\ unconditional patterns in instanceof +compiler.misc.feature.unnamed.classes=\ + unnamed classes + compiler.warn.underscore.as.identifier=\ as of release 9, ''_'' is a keyword, and may not be used as an identifier diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/launcher.properties b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/launcher.properties index 832ff185454..c1d4ac02c90 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/launcher.properties +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/launcher.properties @@ -119,6 +119,14 @@ launcher.err.cant.find.main.method=\ launcher.err.cant.access.main.method=\ can''t access main method in class: {0} +# 0: string +launcher.err.cant.find.constructor=\ + can''t find no argument constructor in class: {0} + +# 0: string +launcher.err.cant.access.constructor=\ + can''t access no argument constructor in class: {0} + # 0: path, 1: object launcher.err.cant.read.file=\ error reading source file {0}: {1} diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeMaker.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeMaker.java index fd67439f835..7cd0df9295b 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeMaker.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeMaker.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -132,6 +132,8 @@ public class TreeMaker implements JCTree.Factory { || node instanceof JCModuleDecl || node instanceof JCSkip || node instanceof JCErroneous + || node instanceof JCMethodDecl + || node instanceof JCVariableDecl || (node instanceof JCExpressionStatement expressionStatement && expressionStatement.expr instanceof JCErroneous), () -> node.getClass().getSimpleName()); @@ -1159,7 +1161,7 @@ public class TreeMaker implements JCTree.Factory { !it.hasNext(); } } - return false; + return sym.kind == TYP && (sym.flags_field & Flags.UNNAMED_CLASS) != 0; } /** The name of synthetic parameter number `i'. diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Convert.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Convert.java index 6e0fd5f76e4..3d36dcf5537 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Convert.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Convert.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -356,8 +356,7 @@ public class Convert { if (start == 0 && end == name.length()) { return name; } - return name.subName( - name.lastIndexOf((byte)'.') + 1, name.getByteLength()); + return name.subName(start, end); } /** Return the last part of a qualified name from its string representation diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java index 50a9f46f32b..d3666f9ee9d 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -193,6 +193,7 @@ public class Names { public final Name module_info; public final Name package_info; public final Name requireNonNull; + public final Name main; // lambda-related public final Name lambda; @@ -388,6 +389,7 @@ public class Names { module_info = fromString("module-info"); package_info = fromString("package-info"); requireNonNull = fromString("requireNonNull"); + main = fromString("main"); //lambda-related lambda = fromString("lambda$"); diff --git a/test/jdk/tools/launcher/InstanceMainTest.java b/test/jdk/tools/launcher/InstanceMainTest.java new file mode 100644 index 00000000000..05973557fc3 --- /dev/null +++ b/test/jdk/tools/launcher/InstanceMainTest.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * @test + * @summary test execution priority of main methods + * @run main InstanceMainTest + */ +public class InstanceMainTest extends TestHelper { + + private static final String[] SOURCES = new String[] { + // static dominating with args + """ + class MainClass { + static void main() { + throw new AssertionError(); + } + static void main(String[] args) { + } + } + """, + + // static dominating instance + """ + class MainClass { + void main(String[] args) { + throw new AssertionError(); + } + static void main() { + } + } + """, + + // instance dominating with args + """ + class MainClass { + void main() { + throw new AssertionError(); + } + void main(String[] args) { + } + } + """, + + // instance no args + """ + class MainClass { + void main() { + } + } + """, + + // unnamed class static dominating with args + """ + static void main() { + throw new AssertionError(); + } + static void main(String[] args) { + } + """, + + // unnamed class static dominating instance + """ + void main(String[] args) { + throw new AssertionError(); + } + static void main() { + } + """, + + // unnamed class instance dominating with args + """ + void main() { + throw new AssertionError(); + } + void main(String[] args) { + } + """, + + // unnamed class instance main no args + """ + void main() { + } + """, + + // instance main dominating super static + """ + class MainClass extends SuperClass { + void main() { + } + } + class SuperClass { + void main(String[] args) { + throw new AssertionError(); + } + } + """, + + // super instance main with args dominating + """ + public class MainClass extends Super { + } + + class Super { + public void main(String... args) { + } + + public void main() { + throw new AssertionError(); + } + } + """, + + // ignore super instance main + """ + public class MainClass extends Super { + public static void main(String... args) { + } + } + + class Super { + public static void main(String... args) { + throw new AssertionError(); + } + } + """, + + // enum main + """ + enum MainClass { + A; + + public static void main() { + } + } + """, + + // record main + """ + record MainClass() { + static void main() { + System.out.println("Done!"); + } + } + """, + // interface main + """ + interface MainClass { + static void main() { + System.out.println("Done!"); + } + } + """ + }; + + public static void main(String... args) throws Exception { + for (String source : SOURCES) { + Files.writeString(Path.of("MainClass.java"), source); + var version = System.getProperty("java.specification.version"); + var tr = doExec(javaCmd, "--enable-preview", "--source", version, "MainClass.java"); + if (!tr.isOK()) { + System.err.println(source); + System.err.println(tr); + throw new AssertionError(); + } + } + } +} diff --git a/test/langtools/tools/javac/diags/examples/UnnamedClass.java b/test/langtools/tools/javac/diags/examples/UnnamedClass.java new file mode 100644 index 00000000000..c78dcf14e6c --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/UnnamedClass.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + // key: compiler.misc.feature.unnamed.classes + // key: compiler.warn.preview.feature.use.plural + // options: -source ${jdk.version} --enable-preview -Xlint:preview + +public static void main(String... args) { +} diff --git a/test/langtools/tools/javac/diags/examples/UnnamedClassBad-Filename.java b/test/langtools/tools/javac/diags/examples/UnnamedClassBad-Filename.java new file mode 100644 index 00000000000..e3bc3d67783 --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/UnnamedClassBad-Filename.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + // key: compiler.err.bad.file.name + // key: compiler.note.preview.filename + // key: compiler.note.preview.recompile + // options: -source ${jdk.version} --enable-preview + +public static void main(String... args) { +} diff --git a/test/langtools/tools/javac/diags/examples/UnnamedClassHasPackage.java b/test/langtools/tools/javac/diags/examples/UnnamedClassHasPackage.java new file mode 100644 index 00000000000..96601f9a945 --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/UnnamedClassHasPackage.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + // key: compiler.err.unnamed.class.should.not.have.package.declaration + // key: compiler.note.preview.filename + // key: compiler.note.preview.recompile + // options: -source ${jdk.version} --enable-preview + +package unnamed.classes; + +public static void main(String... args) { +} diff --git a/test/langtools/tools/javac/diags/examples/UnnamedClassNoMain.java b/test/langtools/tools/javac/diags/examples/UnnamedClassNoMain.java new file mode 100644 index 00000000000..0caacb60e66 --- /dev/null +++ b/test/langtools/tools/javac/diags/examples/UnnamedClassNoMain.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + // key: compiler.err.unnamed.class.does.not.have.main.method + // key: compiler.note.preview.filename + // key: compiler.note.preview.recompile + // options: -source ${jdk.version} --enable-preview + +public void ordinaryMethod() { +} diff --git a/test/langtools/tools/javac/launcher/SourceLauncherTest.java b/test/langtools/tools/javac/launcher/SourceLauncherTest.java index cb7fb59116e..87b691be396 100644 --- a/test/langtools/tools/javac/launcher/SourceLauncherTest.java +++ b/test/langtools/tools/javac/launcher/SourceLauncherTest.java @@ -553,7 +553,7 @@ public class SourceLauncherTest extends TestRunner { tb.writeJavaFiles(base, "class NotPublic { static void main(String... args) { } }"); testError(base.resolve("NotPublic.java"), "", - "error: 'main' method is not declared 'public static'"); + "error: can't find main(String[]) method in class: NotPublic"); } @Test diff --git a/test/langtools/tools/javac/processing/model/element/Anonymous.java b/test/langtools/tools/javac/processing/model/element/Anonymous.java new file mode 100644 index 00000000000..1e34980c5ac --- /dev/null +++ b/test/langtools/tools/javac/processing/model/element/Anonymous.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * A class with no-name. + */ + +void main() { + printMessage(); +} + +private static String horseName = null; + +private static void printMessage() { + if (horseName == null) { + System.out.print("A horse has no name."); + } else + System.out.print("A horse's name is " + horseName); +} + + + diff --git a/test/langtools/tools/javac/processing/model/element/TestUnnamedClass.java b/test/langtools/tools/javac/processing/model/element/TestUnnamedClass.java new file mode 100644 index 00000000000..273186a1c8e --- /dev/null +++ b/test/langtools/tools/javac/processing/model/element/TestUnnamedClass.java @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8306112 + * @summary Test basic processing of unnamed classes. + * @library /tools/javac/lib + * @modules java.compiler + * jdk.compiler + * @build JavacTestingAbstractProcessor TestUnnamedClass + * @compile -processor TestUnnamedClass -proc:only --enable-preview --release ${jdk.version} Anonymous.java + */ + + +import java.lang.annotation.*; +import java.io.Writer; +import java.util.List; +import java.util.Set; +import javax.annotation.processing.*; +import javax.lang.model.element.*; +import javax.lang.model.util.Elements; +import static javax.lang.model.util.ElementFilter.*; +import javax.tools.JavaFileObject; + +/* + * Ideally, this processor would test both the compile-time + * representation of an unnamed class starting from a source file as + * well as the representation starting from a class file. Currently, + * only the source file based view will be tested. + * + * For future work to test the class file based view, an additional jtreg directive like the following could + * be used: + * + * @compile/process -processor TestUnnamedClass -proc:only Anonymous Nameless + */ +@SuppressWarnings("preview") +public class TestUnnamedClass extends JavacTestingAbstractProcessor { + + private static int round = 0; + + public boolean process(Set annotations, + RoundEnvironment roundEnv) { + if (round == 0) { // Check file from comamnd line + checkRoots(roundEnv); + generateUnnamed(); + } + + if (!roundEnv.processingOver()) { // Test generated file(s) + checkRoots(roundEnv); + } + + round++; + return true; + } + + private void checkRoots(RoundEnvironment roundEnv) { + int checks = 0; + for (TypeElement type : typesIn(roundEnv.getRootElements())) { + System.out.println("Checking " + type.getQualifiedName()); + checks++; + checkUnnamedClassProperties(type); + } + if (checks == 0) { + messager.printError("No checking done of any candidate unnamed classes."); + } + } + + private void generateUnnamed() { + try { + String unnamedSource = """ + void main() { + System.out.println("Nameless, but not voiceless."); + } + """; + + JavaFileObject outputFile = processingEnv.getFiler().createSourceFile("Nameless"); + try(Writer w = outputFile.openWriter()) { + w.append(unnamedSource); + } + } catch (java.io.IOException ioe) { + throw new RuntimeException(ioe); + } + } + + /* + * From JEP 445 JLS changes: + * + * "An unnamed class compilation unit implicitly declares a class that satisfies the following + * properties: + * It is always a top level class. + * It is always an unnamed class (it has no canonical or fully qualified name (6.7)). + * It is never abstract (8.1.1.1). + * It is always final (8.1.1.2). + * It is always a member of an unnamed package (7.4.2) and has package access. + * Its direct superclass type is always Object (8.1.4). + * It never has any direct superinterface types (8.1.5). + * + * The body of the class contains every ClassMemberDeclaration + * from the unnamed class compilation unit. It is not possible for + * an unnamed class compilation unit to declare an instance + * initializer, static initializer, or constructor. + * + * It has an implicitly declared default constructor (8.8.9). + * All members of this class, including any implicitly declared + * members, are subject to the usual rules for member declarations + * in a class. + * + * It is a compile-time error if this class does not declare a candidate main method (12.1.4). + */ + void checkUnnamedClassProperties(TypeElement unnamedClass) { + if (unnamedClass.getNestingKind() != NestingKind.TOP_LEVEL) { + messager.printError("Unnamed class is not top-level.", unnamedClass); + } + + if (!unnamedClass.isUnnamed()) { + messager.printError("Unnamed class is _not_ indicated as such.", unnamedClass); + } + + if (unnamedClass.getSimpleName().isEmpty()) { + messager.printError("Unnamed class does have an empty simple name.", unnamedClass); + } + + if (!unnamedClass.getQualifiedName().isEmpty()) { + messager.printError("Unnamed class does _not_ have an empty qualified name.", unnamedClass); + } + + if (unnamedClass.getModifiers().contains(Modifier.ABSTRACT)) { + messager.printError("Unnamed class is abstract.", unnamedClass); + } + + if (!unnamedClass.getModifiers().contains(Modifier.FINAL)) { + messager.printError("Unnamed class is _not_ final.", unnamedClass); + } + + if (!elements.getPackageOf(unnamedClass).isUnnamed()) { + messager.printError("Unnamed class is _not_ in an unnamed package.", unnamedClass); + } + + if (unnamedClass.getModifiers().contains(Modifier.PUBLIC) || + unnamedClass.getModifiers().contains(Modifier.PRIVATE) || + unnamedClass.getModifiers().contains(Modifier.PROTECTED)) { + messager.printError("Unnamed class does _not_ have package access.", unnamedClass); + } + + if ( !types.isSameType(unnamedClass.getSuperclass(), + elements.getTypeElement("java.lang.Object").asType())) { + messager.printError("Unnamed class does _not_ have java.lang.Object as a superclass.", unnamedClass); + } + + if (!unnamedClass.getInterfaces().isEmpty()) { + messager.printError("Unnamed class has superinterfaces.", unnamedClass); + } + + List ctors = constructorsIn(unnamedClass.getEnclosedElements()); + if (ctors.size() != 1 ) { + messager.printError("Did not find exactly one constructor", unnamedClass); + } + + ExecutableElement ctor = ctors.getFirst(); + if (elements.getOrigin(ctor) != Elements.Origin.MANDATED) { + messager.printError("Constructor was not marked as mandated", ctor); + } + + List methods = methodsIn(unnamedClass.getEnclosedElements()); + // Just look for a method named "main"; don't check the other details. + boolean mainFound = false; + Name mainName = elements.getName("main"); + for (var method : methods) { + if (method.getSimpleName().equals(mainName)) { + mainFound = true; + break; + } + } + + if (!mainFound) { + messager.printError("No main mehtod found", unnamedClass); + } + } +} diff --git a/test/langtools/tools/javac/unnamed/UnnamedClassRecovery.java b/test/langtools/tools/javac/unnamed/UnnamedClassRecovery.java new file mode 100644 index 00000000000..2c1d533ab6c --- /dev/null +++ b/test/langtools/tools/javac/unnamed/UnnamedClassRecovery.java @@ -0,0 +1,8 @@ +/** + * @test /nodynamiccopyright/ + * @compile/fail/ref=UnnamedClassRecovery.out -XDrawDiagnostics --enable-preview --source ${jdk.version} UnnamedClassRecovery.java + */ +public void main() { + //the following is intentionally missing a semicolon: + System.err.println("Hello!") +} diff --git a/test/langtools/tools/javac/unnamed/UnnamedClassRecovery.out b/test/langtools/tools/javac/unnamed/UnnamedClassRecovery.out new file mode 100644 index 00000000000..0389b156f01 --- /dev/null +++ b/test/langtools/tools/javac/unnamed/UnnamedClassRecovery.out @@ -0,0 +1,4 @@ +UnnamedClassRecovery.java:7:33: compiler.err.expected: ';' +- compiler.note.preview.filename: UnnamedClassRecovery.java, DEFAULT +- compiler.note.preview.recompile +1 error diff --git a/test/langtools/tools/javac/unnamedclass/NestedEnum.java b/test/langtools/tools/javac/unnamedclass/NestedEnum.java new file mode 100644 index 00000000000..75d4e63f6ab --- /dev/null +++ b/test/langtools/tools/javac/unnamedclass/NestedEnum.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary enums: ensure unnamed class is visible to java.lang.Enum + * @enablePreview + * @compile NestedEnum.java + */ + +enum Foo {A, B} +void main() { + System.out.println(Foo.A); +}