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