8328339: Static import prevents source launcher from finding class with main method

Reviewed-by: jlahoda
This commit is contained in:
Christian Stein 2024-03-22 05:29:20 +00:00
parent 256d48b196
commit 9bc741d04f
4 changed files with 67 additions and 53 deletions

View File

@ -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<String> compileProgram() throws Fault {
void compileProgram() throws Fault {
var units = new ArrayList<JavaFileObject>();
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<String>();
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);
}
/**

View File

@ -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.</strong></p>
*/
public record ProgramDescriptor(ProgramFileObject fileObject, Optional<String> packageName, Path sourceRootPath) {
public record ProgramDescriptor(
ProgramFileObject fileObject,
Optional<String> packageName,
List<String> 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<String>();
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;

View File

@ -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<String> 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<String> 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)));

View File

@ -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);
}