-
Notifications
You must be signed in to change notification settings - Fork 55
/
SourceLauncher.java
279 lines (253 loc) · 11.3 KB
/
SourceLauncher.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
/*
* 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.launcher;
import com.sun.tools.javac.resources.LauncherProperties.Errors;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import jdk.internal.misc.MethodFinder;
import jdk.internal.misc.VM;
/**
* Compiles a source file, and executes the main method it contains.
*
* <p><strong>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.</strong></p>
*/
public final class SourceLauncher {
/**
* Compiles a source file, and executes the main method it contains.
*
* <p>This is normally invoked from the Java launcher, either when
* the {@code --source} option is used, or when the first argument
* that is not part of a runtime option ends in {@code .java}.
*
* <p>The first entry in the {@code args} array is the source file
* to be compiled and run; all subsequent entries are passed as
* arguments to the main method of the first class found in the file.
*
* <p>If any problem occurs before executing the main class, it will
* be reported to the standard error stream, and the JVM will be
* terminated by calling {@code System.exit} with a non-zero return code.
*
* @param args the arguments
* @throws Throwable if the main method throws an exception
*/
public static void main(String... args) throws Throwable {
try {
new SourceLauncher(System.err)
.checkSecurityManager()
.run(VM.getRuntimeArguments(), args);
} catch (Fault f) {
System.err.println(f.getMessage());
System.exit(1);
} catch (InvocationTargetException e) {
// leave VM to handle the stacktrace, in the standard manner
throw e.getCause();
}
}
/** Stream for reporting errors, such as compilation errors. */
private final PrintWriter out;
/**
* Creates an instance of this class, providing a stream to which to report
* any errors.
*
* @param out the stream
*/
public SourceLauncher(PrintStream out) {
this(new PrintWriter(new OutputStreamWriter(out), true));
}
/**
* Creates an instance of this class, providing a stream to which to report
* any errors.
*
* @param out the stream
*/
public SourceLauncher(PrintWriter out) {
this.out = out;
}
/**
* Checks if a security manager is present and throws an exception if so.
* @return this object
* @throws Fault if a security manager is present
*/
@SuppressWarnings("removal")
private SourceLauncher checkSecurityManager() throws Fault {
if (System.getSecurityManager() != null) {
throw new Fault(Errors.SecurityManager);
}
return this;
}
/**
* Compiles a source file, and executes the main method it contains.
*
* <p>The first entry in the {@code args} array is the source file
* to be compiled and run; all subsequent entries are passed as
* arguments to the main method of the first class found in the file.
*
* <p>Options for {@code javac} are obtained by filtering the runtime arguments.
*
* <p>If the main method throws an exception, it will be propagated in an
* {@code InvocationTargetException}. In that case, the stack trace of the
* target exception will be truncated such that the main method will be the
* last entry on the stack. In other words, the stack frames leading up to the
* invocation of the main method will be removed.
*
* @param runtimeArgs the runtime arguments
* @param args the arguments
* @throws Fault if a problem is detected before the main method can be executed
* @throws InvocationTargetException if the main method throws an exception
*/
public Result run(String[] runtimeArgs, String[] args) throws Fault, InvocationTargetException {
Path file = getFile(args);
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();
String[] mainArgs = Arrays.copyOfRange(args, 1, args.length);
var appClass = execute(names, mainArgs, context);
return new Result(appClass, context.getNamesOfCompiledClasses());
}
/**
* Returns the path for the filename found in the first of an array of arguments.
*
* @param args the array
* @return the path, as given in the array of args
* @throws Fault if there is a problem determining the path, or if the file does not exist
*/
private Path getFile(String[] args) throws Fault {
if (args.length == 0) {
// should not happen when invoked from launcher
throw new Fault(Errors.NoArgs);
}
Path file;
try {
file = Paths.get(args[0]);
} catch (InvalidPathException e) {
throw new Fault(Errors.InvalidFilename(args[0]));
}
if (!Files.exists(file)) {
// should not happen when invoked from launcher
throw new Fault(Errors.FileNotFound(file));
}
return file;
}
/**
* 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)
throws Fault, InvocationTargetException {
System.setProperty("jdk.launcher.sourcefile", context.getSourceFileAsString());
ClassLoader parentLoader = ClassLoader.getSystemClassLoader();
// 1. Find a main method in the first class and if there is one - invoke it
Class<?> firstClass;
String firstClassName = topLevelClassNames.getFirst();
try {
ClassLoader loader = context.newClassLoaderFor(parentLoader, firstClassName);
firstClass = Class.forName(firstClassName, false, loader);
} catch (ClassNotFoundException e) {
throw new Fault(Errors.CantFindClass(firstClassName));
}
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();
assert compilationUnitName.endsWith(".java");
var expectedName = compilationUnitName.substring(0, compilationUnitName.length() - 5);
var actualName = topLevelClassNames.stream()
.filter(name -> name.equals(expectedName))
.findFirst()
.orElseThrow(() -> new Fault(Errors.CantFindClass(expectedName)));
Class<?> actualClass;
try {
actualClass = Class.forName(actualName, false, firstClass.getClassLoader());
} catch (ClassNotFoundException ignore) {
throw new Fault(Errors.CantFindClass(actualName));
}
mainMethod = MethodFinder.findMainMethod(actualClass);
if (mainMethod == null) {
throw new Fault(Errors.CantFindMainMethod(actualName));
}
}
// selected main method instance points back to its declaring class
Class<?> mainClass = mainMethod.getDeclaringClass();
String mainClassName = mainClass.getName();
var isStatic = Modifier.isStatic(mainMethod.getModifiers());
Object instance = null;
if (!isStatic) {
Constructor<?> constructor;
try {
constructor = mainClass.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 ? mainClass : instance;
if (mainMethod.getParameterCount() == 0) {
mainMethod.invoke(receiver);
} else {
mainMethod.invoke(receiver, (Object)mainArgs);
}
} catch (IllegalAccessException e) {
throw new Fault(Errors.CantAccessMainMethod(mainClassName));
} catch (InvocationTargetException e) {
// remove stack frames for source launcher
int invocationFrames = e.getStackTrace().length;
Throwable target = e.getCause();
StackTraceElement[] targetTrace = target.getStackTrace();
target.setStackTrace(Arrays.copyOfRange(targetTrace, 0, targetTrace.length - invocationFrames));
throw e;
}
return mainClass;
}
}