• Icon: Sub-task Sub-task
    • Resolution: Fixed
    • Icon: P4 P4
    • None
    • None
    • tools
    • None

      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

            anazarov Andrey Nazarov (Inactive)
            anazarov Andrey Nazarov (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

              Created:
              Updated:
              Resolved: