Coverage rate of diff is 88
changeset: 50453:f91927a2c8d3
user: jjg
date: Thu Jun 07 16:06:49 2018 -0700
summary: 8201274: Launch Single-File Source-Code Programs
com/sun/tools/javac/launcher/Main.java: Main.void main(java.lang.String[])
+ 119 | new Main(System.err).run(VM.getRuntimeArguments(), args);
+ 120 | } catch (Fault f) {
+ 121 | System.err.println(f.getMessage());
+ 122 | System.exit(1);
- 123 | } catch (InvocationTargetException e) {
124 | // leave VM to handle the stacktrace, in the standard manner
- 125 | throw e.getTargetException();
+ 126 | }
+ 127 | }
com/sun/tools/javac/launcher/Main.java: Main.void <init>(java.io.PrintStream)
+ 139 | this(new PrintWriter(new OutputStreamWriter(out), true));
+ 140 | }
com/sun/tools/javac/launcher/Main.java: Main.void <init>(java.io.PrintWriter)
+ 148 | public Main(PrintWriter out) {
+ 149 | this.out = out;
+ 150 | }
151 |
152 | /**
153 | * Compiles a source file, and executes the main method it contains.
154 | *
155 | * <p>The first entry in the {@code args} array is the source file
156 | * to be compiled and run; all subsequent entries are passed as
157 | * arguments to the main method of the first class found in the file.
158 | *
159 | * <p>Options for {@code javac} are obtained by filtering the runtime arguments.
160 | *
161 | * <p>If the main method throws an exception, it will be propagated in an
162 | * {@code InvocationTargetException}. In that case, the stack trace of the
163 | * target exception will be truncated such that the main method will be the
164 | * last entry on the stack. In other words, the stack frames leading up to the
165 | * invocation of the main method will be removed.
166 | *
167 | * @param runtimeArgs the runtime arguments
168 | * @param args the arguments
169 | * @throws Fault if a problem is detected before the main method can be executed
170 | * @throws InvocationTargetException if the main method throws an exception
171 | */
172 | public void run(String[] runtimeArgs, String[] args) throws Fault, InvocationTargetException {
+ 173 | Path file = getFile(args);
174 |
+ 175 | Context context = new Context();
+ 176 | String mainClassName = compile(file, getJavacOpts(runtimeArgs), context);
177 |
+ 178 | String[] appArgs = Arrays.copyOfRange(args, 1, args.length);
+ 179 | execute(mainClassName, appArgs, context);
+ 180 | }
181 |
182 | /**
183 | * Returns the path for the filename found in the first of an array of arguments.
184 | *
185 | * @param args the array
186 | * @return the path
187 | * @throws Fault if there is a problem determining the path, or if the file does not exist
188 | */
189 | private Path getFile(String[] args) throws Fault {
+ 190 | if (args.length == 0) {
191 | // should not happen when invoked from launcher
- 192 | throw new Fault(Errors.NoArgs);
193 | }
194 | Path file;
195 | try {
+ 196 | file = Paths.get(args[0]);
- 197 | } catch (InvalidPathException e) {
- 198 | throw new Fault(Errors.InvalidFilename(args[0]));
+ 199 | }
+ 200 | if (!Files.exists(file)) {
201 | // should not happen when invoked from launcher
- 202 | throw new Fault(Errors.FileNotFound(file));
203 | }
+ 204 | return file;
205 | }
206 |
207 | /**
208 | * Reads a source file, ignoring the first line if it is not a Java source file and
209 | * it begins with {@code #!}.
210 | *
211 | * <p>If it is not a Java source file, and if the first two bytes are {@code #!},
212 | * indicating a "magic number" of an executable text file, the rest of the first line
213 | * up to but not including the newline is ignored. All characters after the first two are
214 | * read in the {@link Charset#defaultCharset default platform encoding}.
215 | *
216 | * @param file the file
217 | * @return a file object containing the content of the file
218 | * @throws Fault if an error occurs while reading the file
219 | */
220 | private JavaFileObject readFile(Path file) throws Fault {
221 | // use a BufferedInputStream to guarantee that we can use mark and reset.
+ 222 | try (BufferedInputStream in = new BufferedInputStream(Files.newInputStream(file))) {
223 | boolean ignoreFirstLine;
+ 224 | if (file.getFileName().toString().endsWith(".java")) {
+ 225 | ignoreFirstLine = false;
226 | } else {
+ 227 | in.mark(2);
+ 228 | ignoreFirstLine = (in.read() == '#') && (in.read() == '!');
+ 229 | if (!ignoreFirstLine) {
- 230 | in.reset();
231 | }
232 | }
+ 233 | try (BufferedReader r = new BufferedReader(new InputStreamReader(in, Charset.defaultCharset()))) {
+ 234 | StringBuilder sb = new StringBuilder();
+ 235 | if (ignoreFirstLine) {
+ 236 | r.readLine();
+ 237 | sb.append("\n"); // preserve line numbers
238 | }
+ 239 | char[] buf = new char[1024];
240 | int n;
+ 241 | while ((n = r.read(buf, 0, buf.length)) != -1) {
+ 242 | sb.append(buf, 0, n);
243 | }
+ 244 | return new SimpleJavaFileObject(file.toUri(), SOURCE) {
245 | @Override
246 | public String getName() {
+ 247 | return file.toString();
248 | }
249 | @Override
250 | public CharSequence getCharContent(boolean ignoreEncodingErrors) {
+ 251 | return sb;
252 | }
253 | @Override
254 | public boolean isNameCompatible(String simpleName, JavaFileObject.Kind kind) {
255 | // reject package-info and module-info; accept other names
+ 256 | return (kind == JavaFileObject.Kind.SOURCE)
+ 257 | && SourceVersion.isIdentifier(simpleName);
258 | }
259 | @Override
260 | public String toString() {
- 261 | return "JavacSourceLauncher[" + file + "]";
262 | }
263 | };
+ 264 | }
+ 265 | } catch (IOException e) {
- 266 | throw new Fault(Errors.CantReadFile(file, e));
267 | }
268 | }
269 |
270 | /**
271 | * Returns the subset of the runtime arguments that are relevant to {@code javac}.
272 | * Generally, the relevant options are those for setting paths and for configuring the
273 | * module system.
274 | *
275 | * @param runtimeArgs the runtime arguments
276 | * @return the subset of the runtime arguments
277 | **/
278 | private List<String> getJavacOpts(String... runtimeArgs) throws Fault {
+ 279 | List<String> javacOpts = new ArrayList<>();
280 |
+ 281 | String sourceOpt = System.getProperty("jdk.internal.javac.source");
+ 282 | if (sourceOpt != null) {
+ 283 | Source source = Source.lookup(sourceOpt);
+ 284 | if (source == null) {
+ 285 | throw new Fault(Errors.InvalidValueForSource(sourceOpt));
286 | }
+ 287 | javacOpts.addAll(List.of("--release", sourceOpt));
288 | }
289 |
+ 290 | for (int i = 0; i < runtimeArgs.length; i++) {
+ 291 | String arg = runtimeArgs[i];
+ 292 | String opt = arg, value = null;
+ 293 | if (arg.startsWith("--")) {
+ 294 | int eq = arg.indexOf('=');
+ 295 | if (eq > 0) {
+ 296 | opt = arg.substring(0, eq);
+ 297 | value = arg.substring(eq + 1);
298 | }
299 | }
+ 300 | switch (opt) {
301 | // The following options all expect a value, either in the following
302 | // position, or after '=', for options beginning "--".
303 | case "--class-path": case "-classpath": case "-cp":
304 | case "--module-path": case "-p":
305 | case "--add-exports":
306 | case "--add-modules":
307 | case "--limit-modules":
308 | case "--patch-module":
309 | case "--upgrade-module-path":
+ 310 | if (value == null) {
+ 311 | if (i== runtimeArgs.length - 1) {
312 | // should not happen when invoked from launcher
- 313 | throw new Fault(Errors.NoValueForOption(opt));
314 | }
+ 315 | value = runtimeArgs[++i];
316 | }
+ 317 | if (opt.equals("--add-modules") && value.equals("ALL-DEFAULT")) {
318 | // this option is only supported at run time;
319 | // it is not required or supported at compile time
+ 320 | break;
321 | }
+ 322 | javacOpts.add(opt);
+ 323 | javacOpts.add(value);
+ 324 | break;
325 | case "--enable-preview":
+ 326 | javacOpts.add(opt);
+ 327 | if (sourceOpt == null) {
+ 328 | throw new Fault(Errors.EnablePreviewRequiresSource);
329 | }
330 | break;
331 | default:
332 | // ignore all other runtime args
333 | }
334 | }
335 |
336 | // add implicit options
+ 337 | javacOpts.add("-proc:none");
338 |
+ 339 | return javacOpts;
340 | }
341 |
342 | /**
343 | * Compiles a source file, placing the class files in a map in memory.
344 | * Any messages generated during compilation will be written to the stream
345 | * provided when this object was created.
346 | *
347 | * @param file the source file
348 | * @param javacOpts compilation options for {@code javac}
349 | * @param context the context for the compilation
350 | * @return the name of the first class found in the source file
351 | * @throws Fault if any compilation errors occur, or if no class was found
352 | */
353 | private String compile(Path file, List<String> javacOpts, Context context) throws Fault {
+ 354 | JavaFileObject fo = readFile(file);
355 |
+ 356 | JavacTool javaCompiler = JavacTool.create();
+ 357 | StandardJavaFileManager stdFileMgr = javaCompiler.getStandardFileManager(null, null, null);
358 | try {
+ 359 | stdFileMgr.setLocation(StandardLocation.SOURCE_PATH, Collections.emptyList());
- 360 | } catch (IOException e) {
- 361 | throw new java.lang.Error("unexpected exception from file manager", e);
+ 362 | }
+ 363 | JavaFileManager fm = context.getFileManager(stdFileMgr);
+ 364 | JavacTask t = javaCompiler.getTask(out, fm, null, javacOpts, null, List.of(fo));
+ 365 | MainClassListener l = new MainClassListener(t);
+ 366 | Boolean ok = t.call();
+ 367 | if (!ok) {
+ 368 | throw new Fault(Errors.CompilationFailed);
369 | }
+ 370 | if (l.mainClass == null) {
+ 371 | throw new Fault(Errors.NoClass);
372 | }
+ 373 | String mainClassName = l.mainClass.getQualifiedName().toString();
+ 374 | return mainClassName;
375 | }
376 |
377 | /**
378 | * Invokes the {@code main} method of a specified class, using a class loader that
379 | * will load recently compiled classes from memory.
380 | *
381 | * @param mainClassName the class to be executed
382 | * @param appArgs the arguments for the {@code main} method
383 | * @param context the context for the class to be executed
384 | * @throws Fault if there is a problem finding or invoking the {@code main} method
385 | * @throws InvocationTargetException if the {@code main} method throws an exception
386 | */
387 | private void execute(String mainClassName, String[] appArgs, Context context)
388 | throws Fault, InvocationTargetException {
+ 389 | ClassLoader cl = context.getClassLoader(ClassLoader.getSystemClassLoader());
390 | try {
+ 391 | Class<?> appClass = Class.forName(mainClassName, true, cl);
+ 392 | if (appClass.getClassLoader() != cl) {
+ 393 | throw new Fault(Errors.UnexpectedClass(mainClassName));
394 | }
+ 395 | Method main = appClass.getDeclaredMethod("main", String[].class);
+ 396 | int PUBLIC_STATIC = Modifier.PUBLIC | Modifier.STATIC;
+ 397 | if ((main.getModifiers() & PUBLIC_STATIC) != PUBLIC_STATIC) {
+ 398 | throw new Fault(Errors.MainNotPublicStatic);
399 | }
+ 400 | if (!main.getReturnType().equals(void.class)) {
+ 401 | throw new Fault(Errors.MainNotVoid);
402 | }
+ 403 | main.setAccessible(true);
+ 404 | main.invoke(0, (Object) appArgs);
- 405 | } catch (ClassNotFoundException e) {
- 406 | throw new Fault(Errors.CantFindClass(mainClassName));
+ 407 | } catch (NoSuchMethodException e) {
+ 408 | throw new Fault(Errors.CantFindMainMethod(mainClassName));
- 409 | } catch (IllegalAccessException e) {
- 410 | throw new Fault(Errors.CantAccessMainMethod(mainClassName));
+ 411 | } catch (InvocationTargetException e) {
412 | // remove stack frames for source launcher
+ 413 | int invocationFrames = e.getStackTrace().length;
+ 414 | Throwable target = e.getTargetException();
+ 415 | StackTraceElement[] targetTrace = target.getStackTrace();
+ 416 | target.setStackTrace(Arrays.copyOfRange(targetTrace, 0, targetTrace.length - invocationFrames));
+ 417 | throw e;
+ 418 | }
+ 419 | }
420 |
421 | private static final String bundleName = "com.sun.tools.javac.resources.launcher";
+ 422 | private ResourceBundle resourceBundle = null;
com/sun/tools/javac/launcher/Main.java: Main.java.lang.String getMessage(com.sun.tools.javac.util.JCDiagnostic$Error)
+ 432 | String key = error.key();
+ 433 | Object[] args = error.getArgs();
434 | try {
+ 435 | if (resourceBundle == null) {
+ 436 | resourceBundle = ResourceBundle.getBundle(bundleName);
+ 437 | errorPrefix = resourceBundle.getString("launcher.error");
438 | }
+ 439 | String resource = resourceBundle.getString(key);
+ 440 | String message = MessageFormat.format(resource, args);
+ 441 | return errorPrefix + message;
- 442 | } catch (MissingResourceException e) {
- 443 | return "Cannot access resource; " + key + Arrays.toString(args);
com/sun/tools/javac/launcher/Main.java: Main.java.lang.String access$000(com.sun.tools.javac.launcher.Main,com.sun.tools.javac.util.JCDiagnostic$Error)
+ 88 |public class Main {
com/sun/tools/javac/launcher/Main.java: Main.void <init>()
+ 473 | private static class Context {
+ 474 | private Map<String, byte[]> inMemoryClasses = new HashMap<>();
com/sun/tools/javac/launcher/Main.java: Main.javax.tools.JavaFileManager getFileManager(javax.tools.StandardJavaFileManager)
+ 477 | return new MemoryFileManager(inMemoryClasses, delegate);
com/sun/tools/javac/launcher/Main.java: Main.java.lang.ClassLoader getClassLoader(java.lang.ClassLoader)
+ 481 | return new MemoryClassLoader(inMemoryClasses, parent);
com/sun/tools/javac/launcher/Main.java: Main.void <init>(com.sun.tools.javac.launcher.Main,com.sun.tools.javac.util.JCDiagnostic$Error)
+ 94 | Fault(Error error) {
+ 95 | super(Main.this.getMessage(error));
+ 96 | }
com/sun/tools/javac/launcher/Main.java: Main.void <init>(com.sun.source.util.JavacTask)
+ 453 | MainClassListener(JavacTask t) {
+ 454 | t.addTaskListener(this);
+ 455 | }
com/sun/tools/javac/launcher/Main.java: Main.void started(com.sun.source.util.TaskEvent)
+ 459 | if (ev.getKind() == TaskEvent.Kind.ANALYZE && mainClass == null) {
+ 460 | TypeElement te = ev.getTypeElement();
+ 461 | if (te.getNestingKind() == NestingKind.TOP_LEVEL) {
+ 462 | mainClass = te;
463 | }
464 | }
+ 465 | }
com/sun/tools/javac/launcher/Main.java: Main.void <init>(java.util.Map,java.lang.ClassLoader)
+ 537 | super(parent);
+ 538 | this.map = map;
+ 539 | }
com/sun/tools/javac/launcher/Main.java: Main.java.lang.Class findClass(java.lang.String)
+ 543 | byte[] bytes = map.get(name);
+ 544 | if (bytes == null) {
- 545 | throw new ClassNotFoundException(name);
546 | }
+ 547 | return defineClass(name, bytes, 0, bytes.length);
com/sun/tools/javac/launcher/Main.java: Main.void <init>(java.util.Map,javax.tools.JavaFileManager)
+ 496 | super(delegate);
+ 497 | this.map = map;
+ 498 | }
com/sun/tools/javac/launcher/Main.java: Main.javax.tools.JavaFileObject getJavaFileForOutput(javax.tools.JavaFileManager$Location,java.lang.String,javax.tools.JavaFileObject$Kind,javax.tools.FileObject)
+ 503 | if (location == StandardLocation.CLASS_OUTPUT && kind == JavaFileObject.Kind.CLASS) {
+ 504 | return createInMemoryClassFile(className);
505 | } else {
- 506 | return super.getJavaFileForOutput(location, className, kind, sibling);
com/sun/tools/javac/launcher/Main.java: Main.javax.tools.JavaFileObject createInMemoryClassFile(java.lang.String)
+ 511 | URI uri = URI.create("memory:///" + className.replace('.', '/') + ".class");
+ 512 | return new SimpleJavaFileObject(uri, JavaFileObject.Kind.CLASS) {
com/sun/tools/javac/launcher/Main.java: Main.java.util.Map access$200(com.sun.tools.javac.launcher.Main$MemoryFileManager)
+ 492 | private static class MemoryFileManager extends ForwardingJavaFileManager<JavaFileManager> {
com/sun/tools/javac/launcher/Main.java: Main.java.io.OutputStream openOutputStream()
+ 515 | return new ByteArrayOutputStream() {
com/sun/tools/javac/launcher/Main.java: Main.void close()
+ 518 | super.close();
+ 519 | map.put(className, toByteArray());
+ 520 | }
sun/launcher/LauncherHelper.java: LauncherHelper.java.lang.Class checkAndLoadMain(boolean,int,java.lang.String)
+ 548 | Class<?> mainClass = null;
+ 549 | switch (mode) {
550 | case LM_MODULE: case LM_SOURCE:
+ 551 | mainClass = loadModuleMainClass(what);
+ 552 | break;
553 | default:
+ 554 | mainClass = loadMainClass(mode, what);
555 | break;
556 | }
sun/launcher/LauncherHelper.java: LauncherHelper.void <clinit>()
505 | // enum LaunchMode { LM_UNKNOWN = 0, LM_CLASS, LM_JAR, LM_MODULE, LM_SOURCE }
511 | private static final int LM_SOURCE = 4;
542 | @SuppressWarnings("fallthrough")
lines: 563 new; 157 covered; 20 not covered; 386 not code; 0 no information
changeset: 50453:f91927a2c8d3
user: jjg
date: Thu Jun 07 16:06:49 2018 -0700
summary: 8201274: Launch Single-File Source-Code Programs
com/sun/tools/javac/launcher/Main.java: Main.void main(java.lang.String[])
+ 119 | new Main(System.err).run(VM.getRuntimeArguments(), args);
+ 120 | } catch (Fault f) {
+ 121 | System.err.println(f.getMessage());
+ 122 | System.exit(1);
- 123 | } catch (InvocationTargetException e) {
124 | // leave VM to handle the stacktrace, in the standard manner
- 125 | throw e.getTargetException();
+ 126 | }
+ 127 | }
com/sun/tools/javac/launcher/Main.java: Main.void <init>(java.io.PrintStream)
+ 139 | this(new PrintWriter(new OutputStreamWriter(out), true));
+ 140 | }
com/sun/tools/javac/launcher/Main.java: Main.void <init>(java.io.PrintWriter)
+ 148 | public Main(PrintWriter out) {
+ 149 | this.out = out;
+ 150 | }
151 |
152 | /**
153 | * Compiles a source file, and executes the main method it contains.
154 | *
155 | * <p>The first entry in the {@code args} array is the source file
156 | * to be compiled and run; all subsequent entries are passed as
157 | * arguments to the main method of the first class found in the file.
158 | *
159 | * <p>Options for {@code javac} are obtained by filtering the runtime arguments.
160 | *
161 | * <p>If the main method throws an exception, it will be propagated in an
162 | * {@code InvocationTargetException}. In that case, the stack trace of the
163 | * target exception will be truncated such that the main method will be the
164 | * last entry on the stack. In other words, the stack frames leading up to the
165 | * invocation of the main method will be removed.
166 | *
167 | * @param runtimeArgs the runtime arguments
168 | * @param args the arguments
169 | * @throws Fault if a problem is detected before the main method can be executed
170 | * @throws InvocationTargetException if the main method throws an exception
171 | */
172 | public void run(String[] runtimeArgs, String[] args) throws Fault, InvocationTargetException {
+ 173 | Path file = getFile(args);
174 |
+ 175 | Context context = new Context();
+ 176 | String mainClassName = compile(file, getJavacOpts(runtimeArgs), context);
177 |
+ 178 | String[] appArgs = Arrays.copyOfRange(args, 1, args.length);
+ 179 | execute(mainClassName, appArgs, context);
+ 180 | }
181 |
182 | /**
183 | * Returns the path for the filename found in the first of an array of arguments.
184 | *
185 | * @param args the array
186 | * @return the path
187 | * @throws Fault if there is a problem determining the path, or if the file does not exist
188 | */
189 | private Path getFile(String[] args) throws Fault {
+ 190 | if (args.length == 0) {
191 | // should not happen when invoked from launcher
- 192 | throw new Fault(Errors.NoArgs);
193 | }
194 | Path file;
195 | try {
+ 196 | file = Paths.get(args[0]);
- 197 | } catch (InvalidPathException e) {
- 198 | throw new Fault(Errors.InvalidFilename(args[0]));
+ 199 | }
+ 200 | if (!Files.exists(file)) {
201 | // should not happen when invoked from launcher
- 202 | throw new Fault(Errors.FileNotFound(file));
203 | }
+ 204 | return file;
205 | }
206 |
207 | /**
208 | * Reads a source file, ignoring the first line if it is not a Java source file and
209 | * it begins with {@code #!}.
210 | *
211 | * <p>If it is not a Java source file, and if the first two bytes are {@code #!},
212 | * indicating a "magic number" of an executable text file, the rest of the first line
213 | * up to but not including the newline is ignored. All characters after the first two are
214 | * read in the {@link Charset#defaultCharset default platform encoding}.
215 | *
216 | * @param file the file
217 | * @return a file object containing the content of the file
218 | * @throws Fault if an error occurs while reading the file
219 | */
220 | private JavaFileObject readFile(Path file) throws Fault {
221 | // use a BufferedInputStream to guarantee that we can use mark and reset.
+ 222 | try (BufferedInputStream in = new BufferedInputStream(Files.newInputStream(file))) {
223 | boolean ignoreFirstLine;
+ 224 | if (file.getFileName().toString().endsWith(".java")) {
+ 225 | ignoreFirstLine = false;
226 | } else {
+ 227 | in.mark(2);
+ 228 | ignoreFirstLine = (in.read() == '#') && (in.read() == '!');
+ 229 | if (!ignoreFirstLine) {
- 230 | in.reset();
231 | }
232 | }
+ 233 | try (BufferedReader r = new BufferedReader(new InputStreamReader(in, Charset.defaultCharset()))) {
+ 234 | StringBuilder sb = new StringBuilder();
+ 235 | if (ignoreFirstLine) {
+ 236 | r.readLine();
+ 237 | sb.append("\n"); // preserve line numbers
238 | }
+ 239 | char[] buf = new char[1024];
240 | int n;
+ 241 | while ((n = r.read(buf, 0, buf.length)) != -1) {
+ 242 | sb.append(buf, 0, n);
243 | }
+ 244 | return new SimpleJavaFileObject(file.toUri(), SOURCE) {
245 | @Override
246 | public String getName() {
+ 247 | return file.toString();
248 | }
249 | @Override
250 | public CharSequence getCharContent(boolean ignoreEncodingErrors) {
+ 251 | return sb;
252 | }
253 | @Override
254 | public boolean isNameCompatible(String simpleName, JavaFileObject.Kind kind) {
255 | // reject package-info and module-info; accept other names
+ 256 | return (kind == JavaFileObject.Kind.SOURCE)
+ 257 | && SourceVersion.isIdentifier(simpleName);
258 | }
259 | @Override
260 | public String toString() {
- 261 | return "JavacSourceLauncher[" + file + "]";
262 | }
263 | };
+ 264 | }
+ 265 | } catch (IOException e) {
- 266 | throw new Fault(Errors.CantReadFile(file, e));
267 | }
268 | }
269 |
270 | /**
271 | * Returns the subset of the runtime arguments that are relevant to {@code javac}.
272 | * Generally, the relevant options are those for setting paths and for configuring the
273 | * module system.
274 | *
275 | * @param runtimeArgs the runtime arguments
276 | * @return the subset of the runtime arguments
277 | **/
278 | private List<String> getJavacOpts(String... runtimeArgs) throws Fault {
+ 279 | List<String> javacOpts = new ArrayList<>();
280 |
+ 281 | String sourceOpt = System.getProperty("jdk.internal.javac.source");
+ 282 | if (sourceOpt != null) {
+ 283 | Source source = Source.lookup(sourceOpt);
+ 284 | if (source == null) {
+ 285 | throw new Fault(Errors.InvalidValueForSource(sourceOpt));
286 | }
+ 287 | javacOpts.addAll(List.of("--release", sourceOpt));
288 | }
289 |
+ 290 | for (int i = 0; i < runtimeArgs.length; i++) {
+ 291 | String arg = runtimeArgs[i];
+ 292 | String opt = arg, value = null;
+ 293 | if (arg.startsWith("--")) {
+ 294 | int eq = arg.indexOf('=');
+ 295 | if (eq > 0) {
+ 296 | opt = arg.substring(0, eq);
+ 297 | value = arg.substring(eq + 1);
298 | }
299 | }
+ 300 | switch (opt) {
301 | // The following options all expect a value, either in the following
302 | // position, or after '=', for options beginning "--".
303 | case "--class-path": case "-classpath": case "-cp":
304 | case "--module-path": case "-p":
305 | case "--add-exports":
306 | case "--add-modules":
307 | case "--limit-modules":
308 | case "--patch-module":
309 | case "--upgrade-module-path":
+ 310 | if (value == null) {
+ 311 | if (i== runtimeArgs.length - 1) {
312 | // should not happen when invoked from launcher
- 313 | throw new Fault(Errors.NoValueForOption(opt));
314 | }
+ 315 | value = runtimeArgs[++i];
316 | }
+ 317 | if (opt.equals("--add-modules") && value.equals("ALL-DEFAULT")) {
318 | // this option is only supported at run time;
319 | // it is not required or supported at compile time
+ 320 | break;
321 | }
+ 322 | javacOpts.add(opt);
+ 323 | javacOpts.add(value);
+ 324 | break;
325 | case "--enable-preview":
+ 326 | javacOpts.add(opt);
+ 327 | if (sourceOpt == null) {
+ 328 | throw new Fault(Errors.EnablePreviewRequiresSource);
329 | }
330 | break;
331 | default:
332 | // ignore all other runtime args
333 | }
334 | }
335 |
336 | // add implicit options
+ 337 | javacOpts.add("-proc:none");
338 |
+ 339 | return javacOpts;
340 | }
341 |
342 | /**
343 | * Compiles a source file, placing the class files in a map in memory.
344 | * Any messages generated during compilation will be written to the stream
345 | * provided when this object was created.
346 | *
347 | * @param file the source file
348 | * @param javacOpts compilation options for {@code javac}
349 | * @param context the context for the compilation
350 | * @return the name of the first class found in the source file
351 | * @throws Fault if any compilation errors occur, or if no class was found
352 | */
353 | private String compile(Path file, List<String> javacOpts, Context context) throws Fault {
+ 354 | JavaFileObject fo = readFile(file);
355 |
+ 356 | JavacTool javaCompiler = JavacTool.create();
+ 357 | StandardJavaFileManager stdFileMgr = javaCompiler.getStandardFileManager(null, null, null);
358 | try {
+ 359 | stdFileMgr.setLocation(StandardLocation.SOURCE_PATH, Collections.emptyList());
- 360 | } catch (IOException e) {
- 361 | throw new java.lang.Error("unexpected exception from file manager", e);
+ 362 | }
+ 363 | JavaFileManager fm = context.getFileManager(stdFileMgr);
+ 364 | JavacTask t = javaCompiler.getTask(out, fm, null, javacOpts, null, List.of(fo));
+ 365 | MainClassListener l = new MainClassListener(t);
+ 366 | Boolean ok = t.call();
+ 367 | if (!ok) {
+ 368 | throw new Fault(Errors.CompilationFailed);
369 | }
+ 370 | if (l.mainClass == null) {
+ 371 | throw new Fault(Errors.NoClass);
372 | }
+ 373 | String mainClassName = l.mainClass.getQualifiedName().toString();
+ 374 | return mainClassName;
375 | }
376 |
377 | /**
378 | * Invokes the {@code main} method of a specified class, using a class loader that
379 | * will load recently compiled classes from memory.
380 | *
381 | * @param mainClassName the class to be executed
382 | * @param appArgs the arguments for the {@code main} method
383 | * @param context the context for the class to be executed
384 | * @throws Fault if there is a problem finding or invoking the {@code main} method
385 | * @throws InvocationTargetException if the {@code main} method throws an exception
386 | */
387 | private void execute(String mainClassName, String[] appArgs, Context context)
388 | throws Fault, InvocationTargetException {
+ 389 | ClassLoader cl = context.getClassLoader(ClassLoader.getSystemClassLoader());
390 | try {
+ 391 | Class<?> appClass = Class.forName(mainClassName, true, cl);
+ 392 | if (appClass.getClassLoader() != cl) {
+ 393 | throw new Fault(Errors.UnexpectedClass(mainClassName));
394 | }
+ 395 | Method main = appClass.getDeclaredMethod("main", String[].class);
+ 396 | int PUBLIC_STATIC = Modifier.PUBLIC | Modifier.STATIC;
+ 397 | if ((main.getModifiers() & PUBLIC_STATIC) != PUBLIC_STATIC) {
+ 398 | throw new Fault(Errors.MainNotPublicStatic);
399 | }
+ 400 | if (!main.getReturnType().equals(void.class)) {
+ 401 | throw new Fault(Errors.MainNotVoid);
402 | }
+ 403 | main.setAccessible(true);
+ 404 | main.invoke(0, (Object) appArgs);
- 405 | } catch (ClassNotFoundException e) {
- 406 | throw new Fault(Errors.CantFindClass(mainClassName));
+ 407 | } catch (NoSuchMethodException e) {
+ 408 | throw new Fault(Errors.CantFindMainMethod(mainClassName));
- 409 | } catch (IllegalAccessException e) {
- 410 | throw new Fault(Errors.CantAccessMainMethod(mainClassName));
+ 411 | } catch (InvocationTargetException e) {
412 | // remove stack frames for source launcher
+ 413 | int invocationFrames = e.getStackTrace().length;
+ 414 | Throwable target = e.getTargetException();
+ 415 | StackTraceElement[] targetTrace = target.getStackTrace();
+ 416 | target.setStackTrace(Arrays.copyOfRange(targetTrace, 0, targetTrace.length - invocationFrames));
+ 417 | throw e;
+ 418 | }
+ 419 | }
420 |
421 | private static final String bundleName = "com.sun.tools.javac.resources.launcher";
+ 422 | private ResourceBundle resourceBundle = null;
com/sun/tools/javac/launcher/Main.java: Main.java.lang.String getMessage(com.sun.tools.javac.util.JCDiagnostic$Error)
+ 432 | String key = error.key();
+ 433 | Object[] args = error.getArgs();
434 | try {
+ 435 | if (resourceBundle == null) {
+ 436 | resourceBundle = ResourceBundle.getBundle(bundleName);
+ 437 | errorPrefix = resourceBundle.getString("launcher.error");
438 | }
+ 439 | String resource = resourceBundle.getString(key);
+ 440 | String message = MessageFormat.format(resource, args);
+ 441 | return errorPrefix + message;
- 442 | } catch (MissingResourceException e) {
- 443 | return "Cannot access resource; " + key + Arrays.toString(args);
com/sun/tools/javac/launcher/Main.java: Main.java.lang.String access$000(com.sun.tools.javac.launcher.Main,com.sun.tools.javac.util.JCDiagnostic$Error)
+ 88 |public class Main {
com/sun/tools/javac/launcher/Main.java: Main.void <init>()
+ 473 | private static class Context {
+ 474 | private Map<String, byte[]> inMemoryClasses = new HashMap<>();
com/sun/tools/javac/launcher/Main.java: Main.javax.tools.JavaFileManager getFileManager(javax.tools.StandardJavaFileManager)
+ 477 | return new MemoryFileManager(inMemoryClasses, delegate);
com/sun/tools/javac/launcher/Main.java: Main.java.lang.ClassLoader getClassLoader(java.lang.ClassLoader)
+ 481 | return new MemoryClassLoader(inMemoryClasses, parent);
com/sun/tools/javac/launcher/Main.java: Main.void <init>(com.sun.tools.javac.launcher.Main,com.sun.tools.javac.util.JCDiagnostic$Error)
+ 94 | Fault(Error error) {
+ 95 | super(Main.this.getMessage(error));
+ 96 | }
com/sun/tools/javac/launcher/Main.java: Main.void <init>(com.sun.source.util.JavacTask)
+ 453 | MainClassListener(JavacTask t) {
+ 454 | t.addTaskListener(this);
+ 455 | }
com/sun/tools/javac/launcher/Main.java: Main.void started(com.sun.source.util.TaskEvent)
+ 459 | if (ev.getKind() == TaskEvent.Kind.ANALYZE && mainClass == null) {
+ 460 | TypeElement te = ev.getTypeElement();
+ 461 | if (te.getNestingKind() == NestingKind.TOP_LEVEL) {
+ 462 | mainClass = te;
463 | }
464 | }
+ 465 | }
com/sun/tools/javac/launcher/Main.java: Main.void <init>(java.util.Map,java.lang.ClassLoader)
+ 537 | super(parent);
+ 538 | this.map = map;
+ 539 | }
com/sun/tools/javac/launcher/Main.java: Main.java.lang.Class findClass(java.lang.String)
+ 543 | byte[] bytes = map.get(name);
+ 544 | if (bytes == null) {
- 545 | throw new ClassNotFoundException(name);
546 | }
+ 547 | return defineClass(name, bytes, 0, bytes.length);
com/sun/tools/javac/launcher/Main.java: Main.void <init>(java.util.Map,javax.tools.JavaFileManager)
+ 496 | super(delegate);
+ 497 | this.map = map;
+ 498 | }
com/sun/tools/javac/launcher/Main.java: Main.javax.tools.JavaFileObject getJavaFileForOutput(javax.tools.JavaFileManager$Location,java.lang.String,javax.tools.JavaFileObject$Kind,javax.tools.FileObject)
+ 503 | if (location == StandardLocation.CLASS_OUTPUT && kind == JavaFileObject.Kind.CLASS) {
+ 504 | return createInMemoryClassFile(className);
505 | } else {
- 506 | return super.getJavaFileForOutput(location, className, kind, sibling);
com/sun/tools/javac/launcher/Main.java: Main.javax.tools.JavaFileObject createInMemoryClassFile(java.lang.String)
+ 511 | URI uri = URI.create("memory:///" + className.replace('.', '/') + ".class");
+ 512 | return new SimpleJavaFileObject(uri, JavaFileObject.Kind.CLASS) {
com/sun/tools/javac/launcher/Main.java: Main.java.util.Map access$200(com.sun.tools.javac.launcher.Main$MemoryFileManager)
+ 492 | private static class MemoryFileManager extends ForwardingJavaFileManager<JavaFileManager> {
com/sun/tools/javac/launcher/Main.java: Main.java.io.OutputStream openOutputStream()
+ 515 | return new ByteArrayOutputStream() {
com/sun/tools/javac/launcher/Main.java: Main.void close()
+ 518 | super.close();
+ 519 | map.put(className, toByteArray());
+ 520 | }
sun/launcher/LauncherHelper.java: LauncherHelper.java.lang.Class checkAndLoadMain(boolean,int,java.lang.String)
+ 548 | Class<?> mainClass = null;
+ 549 | switch (mode) {
550 | case LM_MODULE: case LM_SOURCE:
+ 551 | mainClass = loadModuleMainClass(what);
+ 552 | break;
553 | default:
+ 554 | mainClass = loadMainClass(mode, what);
555 | break;
556 | }
sun/launcher/LauncherHelper.java: LauncherHelper.void <clinit>()
505 | // enum LaunchMode { LM_UNKNOWN = 0, LM_CLASS, LM_JAR, LM_MODULE, LM_SOURCE }
511 | private static final int LM_SOURCE = 4;
542 | @SuppressWarnings("fallthrough")
lines: 563 new; 157 covered; 20 not covered; 386 not code; 0 no information