From 9bc741d04f0c2e731f67ac21144812a55d0ea03d Mon Sep 17 00:00:00 2001
From: Christian Stein
Date: Fri, 22 Mar 2024 05:29:20 +0000
Subject: [PATCH] 8328339: Static import prevents source launcher from finding
class with main method
Reviewed-by: jlahoda
---
.../tools/javac/launcher/MemoryContext.java | 33 +--------------
.../javac/launcher/ProgramDescriptor.java | 40 ++++++++++++++-----
.../tools/javac/launcher/SourceLauncher.java | 22 +++++-----
.../javac/launcher/SourceLauncherTest.java | 25 +++++++++++-
4 files changed, 67 insertions(+), 53 deletions(-)
diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/MemoryContext.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/MemoryContext.java
index 5b746e5ac4e..f804174c711 100644
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/MemoryContext.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/MemoryContext.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2024, 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
@@ -34,9 +34,6 @@ import com.sun.tools.javac.resources.LauncherProperties.Errors;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.Context.Factory;
-import javax.lang.model.element.ElementKind;
-import javax.lang.model.element.NestingKind;
-import javax.lang.model.element.TypeElement;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.StandardLocation;
@@ -112,10 +109,9 @@ final class MemoryContext {
* Any messages generated during compilation will be written to the stream
* provided when this object was created.
*
- * @return the list of top-level types defined in the source file
* @throws Fault if any compilation errors occur, or if no class was found
*/
- List compileProgram() throws Fault {
+ void compileProgram() throws Fault {
var units = new ArrayList();
units.add(descriptor.fileObject());
if (descriptor.isModular()) {
@@ -126,35 +122,10 @@ final class MemoryContext {
var context = new Context();
MemoryPreview.registerInstance(context);
var task = compiler.getTask(out, memoryFileManager, null, opts, null, units, context);
- var fileUri = descriptor.fileObject().toUri();
- var names = new ArrayList();
- task.addTaskListener(new TaskListener() {
- @Override
- public void started(TaskEvent event) {
- if (event.getKind() != TaskEvent.Kind.ANALYZE) return;
- TypeElement element = event.getTypeElement();
- if (element.getNestingKind() != NestingKind.TOP_LEVEL) return;
- JavaFileObject source = event.getSourceFile();
- if (source == null) return;
- if (!source.toUri().equals(fileUri)) return;
- ElementKind kind = element.getKind();
- if (kind != ElementKind.CLASS
- && kind != ElementKind.ENUM
- && kind != ElementKind.INTERFACE
- && kind != ElementKind.RECORD)
- return;
- var name = element.getQualifiedName().toString();
- names.add(name);
- }
- });
var ok = task.call();
if (!ok) {
throw new Fault(Errors.CompilationFailed);
}
- if (names.isEmpty()) {
- throw new Fault(Errors.NoClass);
- }
- return List.copyOf(names);
}
/**
diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/ProgramDescriptor.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/ProgramDescriptor.java
index 9c9831ecedf..087b1708acd 100644
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/ProgramDescriptor.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/ProgramDescriptor.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2024, 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
@@ -25,6 +25,7 @@
package com.sun.tools.javac.launcher;
+import com.sun.source.tree.ClassTree;
import com.sun.tools.javac.api.JavacTool;
import com.sun.tools.javac.resources.LauncherProperties.Errors;
@@ -33,6 +34,7 @@ import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@@ -46,30 +48,46 @@ import java.util.TreeSet;
* risk. This code and its internal interfaces are subject to change
* or deletion without notice.
*/
-public record ProgramDescriptor(ProgramFileObject fileObject, Optional packageName, Path sourceRootPath) {
+public record ProgramDescriptor(
+ ProgramFileObject fileObject,
+ Optional packageName,
+ List qualifiedTypeNames,
+ Path sourceRootPath) {
static ProgramDescriptor of(ProgramFileObject fileObject) throws Fault {
var file = fileObject.getFile();
+ var packageName = ""; // empty string will be converted into an empty optional
+ var packageNameAndDot = ""; // empty string or packageName + '.'
+ var qualifiedTypeNames = new ArrayList();
try {
var compiler = JavacTool.create();
var standardFileManager = compiler.getStandardFileManager(null, null, null);
var units = List.of(fileObject);
var task = compiler.getTask(null, standardFileManager, diagnostic -> {}, null, null, units);
- for (var tree : task.parse()) {
- var packageTree = tree.getPackage();
- if (packageTree != null) {
- var packageName = packageTree.getPackageName().toString();
- var root = computeSourceRootPath(file, packageName);
- return new ProgramDescriptor(fileObject, Optional.of(packageName), root);
+ var tree = task.parse().iterator().next(); // single compilation unit
+ var packageTree = tree.getPackage();
+ if (packageTree != null) {
+ packageName = packageTree.getPackageName().toString();
+ packageNameAndDot = packageName + '.';
+ }
+ for (var type : tree.getTypeDecls()) {
+ if (type instanceof ClassTree classType) {
+ qualifiedTypeNames.add(packageNameAndDot + classType.getSimpleName());
}
}
} catch (IOException ignore) {
// fall through to let actual compilation determine the error message
}
- var root = computeSourceRootPath(file, "");
- return new ProgramDescriptor(fileObject, Optional.empty(), root);
+ if (qualifiedTypeNames.isEmpty()) {
+ throw new Fault(Errors.NoClass);
+ }
+ return new ProgramDescriptor(
+ fileObject,
+ packageName.isEmpty() ? Optional.empty() : Optional.of(packageName),
+ List.copyOf(qualifiedTypeNames),
+ computeSourceRootPath(file, packageName));
}
- public static Path computeSourceRootPath(Path program, String packageName) {
+ public static Path computeSourceRootPath(Path program, String packageName) throws Fault {
var absolute = program.normalize().toAbsolutePath();
var absoluteRoot = absolute.getRoot();
assert absoluteRoot != null;
diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/SourceLauncher.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/SourceLauncher.java
index 75dc779cb33..4ae6b841542 100644
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/SourceLauncher.java
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/launcher/SourceLauncher.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2024, 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
@@ -147,10 +147,10 @@ public final class SourceLauncher {
ProgramDescriptor program = ProgramDescriptor.of(ProgramFileObject.of(file));
RelevantJavacOptions options = RelevantJavacOptions.of(program, runtimeArgs);
MemoryContext context = new MemoryContext(out, program, options);
- List names = context.compileProgram();
+ context.compileProgram();
String[] mainArgs = Arrays.copyOfRange(args, 1, args.length);
- var appClass = execute(names, mainArgs, context);
+ var appClass = execute(context, mainArgs);
return new Result(appClass, context.getNamesOfCompiledClasses());
}
@@ -184,20 +184,20 @@ public final class SourceLauncher {
* Invokes the {@code main} method of a program class, using a class loader that
* will load recently compiled classes from memory.
*
- * @param topLevelClassNames the names of classes in the program compilation unit
* @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 Class> execute(List topLevelClassNames, String[] mainArgs, MemoryContext context)
+ private Class> execute(MemoryContext context, String[] mainArgs)
throws Fault, InvocationTargetException {
System.setProperty("jdk.launcher.sourcefile", context.getSourceFileAsString());
ClassLoader parentLoader = ClassLoader.getSystemClassLoader();
+ ProgramDescriptor program = context.getProgramDescriptor();
// 1. Find a main method in the first class and if there is one - invoke it
Class> firstClass;
- String firstClassName = topLevelClassNames.getFirst();
+ String firstClassName = program.qualifiedTypeNames().getFirst();
try {
ClassLoader loader = context.newClassLoaderFor(parentLoader, firstClassName);
firstClass = Class.forName(firstClassName, false, loader);
@@ -208,10 +208,14 @@ public final class SourceLauncher {
Method mainMethod = MethodFinder.findMainMethod(firstClass);
if (mainMethod == null) {
// 2. If the first class doesn't have a main method, look for a class with a matching name
- var compilationUnitName = context.getProgramDescriptor().fileObject().getFile().getFileName().toString();
+ var compilationUnitName = program.fileObject().getFile().getFileName().toString();
assert compilationUnitName.endsWith(".java");
- var expectedName = compilationUnitName.substring(0, compilationUnitName.length() - 5);
- var actualName = topLevelClassNames.stream()
+ var expectedSimpleName = compilationUnitName.substring(0, compilationUnitName.length() - 5);
+ var expectedPackageName = program.packageName().orElse("");
+ var expectedName = expectedPackageName.isEmpty()
+ ? expectedSimpleName
+ : expectedPackageName + '.' + expectedSimpleName;
+ var actualName = program.qualifiedTypeNames().stream()
.filter(name -> name.equals(expectedName))
.findFirst()
.orElseThrow(() -> new Fault(Errors.CantFindClass(expectedName)));
diff --git a/test/langtools/tools/javac/launcher/SourceLauncherTest.java b/test/langtools/tools/javac/launcher/SourceLauncherTest.java
index cfa6c8ec9e5..6534f9f4dd0 100644
--- a/test/langtools/tools/javac/launcher/SourceLauncherTest.java
+++ b/test/langtools/tools/javac/launcher/SourceLauncherTest.java
@@ -23,7 +23,7 @@
/*
* @test
- * @bug 8192920 8204588 8246774 8248843 8268869 8235876
+ * @bug 8192920 8204588 8246774 8248843 8268869 8235876 8328339
* @summary Test source launcher
* @library /tools/lib
* @enablePreview
@@ -110,6 +110,27 @@ public class SourceLauncherTest extends TestRunner {
testSuccess(base.resolve("hello").resolve("World.java"), "Hello World! [1, 2, 3]\n");
}
+ @Test
+ public void testHelloWorldInPackageWithStaticImport(Path base) throws IOException {
+ tb.writeJavaFiles(base,
+ """
+ package hello;
+ import static hello.Helper.*;
+ import java.util.Arrays;
+ class World {
+ public static void main(String... args) {
+ m(args);
+ }
+ }
+ class Helper {
+ static void m(String... args) {
+ System.out.println("Hello World! " + Arrays.toString(args));
+ }
+ }
+ """);
+ testSuccess(base.resolve("hello").resolve("World.java"), "Hello World! [1, 2, 3]\n");
+ }
+
@Test
public void testHelloWorldWithAux(Path base) throws IOException {
tb.writeJavaFiles(base,
@@ -300,7 +321,7 @@ public class SourceLauncherTest extends TestRunner {
public void testMismatchOfPathAndPackage(Path base) throws IOException {
Files.createDirectories(base);
Path file = base.resolve("MismatchOfPathAndPackage.java");
- Files.write(file, List.of("package p;"));
+ Files.write(file, List.of("package p; class MismatchOfPathAndPackage {}"));
testError(file, "", "error: end of path to source file does not match its package name p: " + file);
}