1 /*
   2  * Copyright (c) 1999, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.tools.javac.main;
  27 
  28 import java.io.FileNotFoundException;
  29 import java.io.IOException;
  30 import java.io.InputStream;
  31 import java.io.PrintWriter;
  32 import java.net.URL;
  33 import java.nio.file.NoSuchFileException;
  34 import java.security.DigestInputStream;
  35 import java.security.MessageDigest;
  36 import java.security.NoSuchAlgorithmException;
  37 import java.util.Set;
  38 
  39 import javax.tools.JavaFileManager;
  40 
  41 import com.sun.tools.javac.api.BasicJavacTask;
  42 import com.sun.tools.javac.file.CacheFSInfo;
  43 import com.sun.tools.javac.file.BaseFileManager;
  44 import com.sun.tools.javac.file.JavacFileManager;
  45 import com.sun.tools.javac.jvm.Target;
  46 import com.sun.tools.javac.platform.PlatformDescription;
  47 import com.sun.tools.javac.processing.AnnotationProcessingError;
  48 import com.sun.tools.javac.util.*;
  49 import com.sun.tools.javac.util.Log.PrefixKind;
  50 import com.sun.tools.javac.util.Log.WriterKind;
  51 
  52 /** This class provides a command line interface to the javac compiler.
  53  *
  54  *  <p><b>This is NOT part of any supported API.
  55  *  If you write code that depends on this, you do so at your own risk.
  56  *  This code and its internal interfaces are subject to change or
  57  *  deletion without notice.</b>
  58  */
  59 public class Main {
  60 
  61     /** The name of the compiler, for use in diagnostics.
  62      */
  63     String ownName;
  64 
  65     /** The writer to use for normal output.
  66      */
  67     PrintWriter stdOut;
  68 
  69     /** The writer to use for diagnostic output.
  70      */
  71     PrintWriter stdErr;
  72 
  73     /** The log to use for diagnostic output.
  74      */
  75     public Log log;
  76 
  77     /**
  78      * If true, certain errors will cause an exception, such as command line
  79      * arg errors, or exceptions in user provided code.
  80      */
  81     boolean apiMode;
  82 
  83 
  84     /** Result codes.
  85      */
  86     public enum Result {
  87         OK(0),        // Compilation completed with no errors.
  88         ERROR(1),     // Completed but reported errors.
  89         CMDERR(2),    // Bad command-line arguments
  90         SYSERR(3),    // System error or resource exhaustion.
  91         ABNORMAL(4);  // Compiler terminated abnormally
  92 
  93         Result(int exitCode) {
  94             this.exitCode = exitCode;
  95         }
  96 
  97         public boolean isOK() {
  98             return (exitCode == 0);
  99         }
 100 
 101         public final int exitCode;
 102     }
 103 
 104     /**
 105      * Construct a compiler instance.
 106      * @param name the name of this tool
 107      */
 108     public Main(String name) {
 109         this.ownName = name;
 110     }
 111 
 112     /**
 113      * Construct a compiler instance.
 114      * @param name the name of this tool
 115      * @param out a stream to which to write messages
 116      */
 117     public Main(String name, PrintWriter out) {
 118         this.ownName = name;
 119         this.stdOut = this.stdErr = out;
 120     }
 121 
 122     /**
 123      * Construct a compiler instance.
 124      * @param name the name of this tool
 125      * @param out a stream to which to write expected output
 126      * @param err a stream to which to write diagnostic output
 127      */
 128     public Main(String name, PrintWriter out, PrintWriter err) {
 129         this.ownName = name;
 130         this.stdOut = out;
 131         this.stdErr = err;
 132     }
 133 
 134     /** Report a usage error.
 135      */
 136     void error(String key, Object... args) {
 137         if (apiMode) {
 138             String msg = log.localize(PrefixKind.JAVAC, key, args);
 139             throw new PropagatedException(new IllegalStateException(msg));
 140         }
 141         warning(key, args);
 142         log.printLines(PrefixKind.JAVAC, "msg.usage", ownName);
 143     }
 144 
 145     /** Report a warning.
 146      */
 147     void warning(String key, Object... args) {
 148         log.printRawLines(ownName + ": " + log.localize(PrefixKind.JAVAC, key, args));
 149     }
 150 
 151 
 152     /**
 153      * Programmatic interface for main function.
 154      * @param args  the command line parameters
 155      * @return the result of the compilation
 156      */
 157     public Result compile(String[] args) {
 158         Context context = new Context();
 159         JavacFileManager.preRegister(context); // can't create it until Log has been set up
 160         Result result = compile(args, context);
 161         if (fileManager instanceof JavacFileManager) {
 162             try {
 163                 // A fresh context was created above, so jfm must be a JavacFileManager
 164                 ((JavacFileManager)fileManager).close();
 165             } catch (IOException ex) {
 166                 bugMessage(ex);
 167             }
 168         }
 169         return result;
 170     }
 171 
 172     /**
 173      * Internal version of compile, allowing context to be provided.
 174      * Note that the context needs to have a file manager set up.
 175      * @param argv  the command line parameters
 176      * @param context the context
 177      * @return the result of the compilation
 178      */
 179     public Result compile(String[] argv, Context context) {
 180         if (stdOut != null) {
 181             context.put(Log.outKey, stdOut);
 182         }
 183 
 184         if (stdErr != null) {
 185             context.put(Log.errKey, stdErr);
 186         }
 187 
 188         log = Log.instance(context);
 189 
 190         if (argv.length == 0) {
 191             OptionHelper h = new OptionHelper.GrumpyHelper(log) {
 192                 @Override
 193                 public String getOwnName() { return ownName; }
 194                 @Override
 195                 public void put(String name, String value) { }
 196             };
 197             Option.HELP.process(h, "-help");
 198             return Result.CMDERR;
 199         }
 200 
 201         // prefix argv with contents of _JAVAC_OPTIONS if set
 202         String envOpt = System.getenv("_JAVAC_OPTIONS");
 203         if (envOpt != null && !envOpt.trim().isEmpty()) {
 204             String[] envv = envOpt.split("\\s+");
 205             String[] result = new String[envv.length + argv.length];
 206             System.arraycopy(envv, 0, result, 0, envv.length);
 207             System.arraycopy(argv, 0, result, envv.length, argv.length);
 208             argv = result;
 209         }
 210 
 211         // expand @-files
 212         try {
 213             argv = CommandLine.parse(argv);
 214         } catch (FileNotFoundException | NoSuchFileException e) {
 215             warning("err.file.not.found", e.getMessage());
 216             return Result.SYSERR;
 217         } catch (IOException ex) {
 218             log.printLines(PrefixKind.JAVAC, "msg.io");
 219             ex.printStackTrace(log.getWriter(WriterKind.NOTICE));
 220             return Result.SYSERR;
 221         }
 222 
 223         Arguments args = Arguments.instance(context);
 224         args.init(ownName, argv);
 225 
 226         if (log.nerrors > 0)
 227             return Result.CMDERR;
 228 
 229         Options options = Options.instance(context);
 230 
 231         // init Log
 232         boolean forceStdOut = options.isSet("stdout");
 233         if (forceStdOut) {
 234             log.flush();
 235             log.setWriters(new PrintWriter(System.out, true));
 236         }
 237 
 238         // init CacheFSInfo
 239         // allow System property in following line as a Mustang legacy
 240         boolean batchMode = (options.isUnset("nonBatchMode")
 241                     && System.getProperty("nonBatchMode") == null);
 242         if (batchMode)
 243             CacheFSInfo.preRegister(context);
 244 
 245         boolean ok = true;
 246 
 247         // init file manager
 248         fileManager = context.get(JavaFileManager.class);
 249         if (fileManager instanceof BaseFileManager) {
 250             ((BaseFileManager) fileManager).setContext(context); // reinit with options
 251             ok &= ((BaseFileManager) fileManager).handleOptions(args.getDeferredFileManagerOptions());
 252         }
 253 
 254         // handle this here so it works even if no other options given
 255         String showClass = options.get("showClass");
 256         if (showClass != null) {
 257             if (showClass.equals("showClass")) // no value given for option
 258                 showClass = "com.sun.tools.javac.Main";
 259             showClass(showClass);
 260         }
 261 
 262         ok &= args.validate();
 263         if (!ok || log.nerrors > 0)
 264             return Result.CMDERR;
 265 
 266         if (args.isEmpty())
 267             return Result.OK;
 268 
 269         // init Dependencies
 270         if (options.isSet("debug.completionDeps")) {
 271             Dependencies.GraphDependencies.preRegister(context);
 272         }
 273 
 274         // init plugins
 275         Set<List<String>> pluginOpts = args.getPluginOpts();
 276         if (!pluginOpts.isEmpty() || context.get(PlatformDescription.class) != null) {
 277             BasicJavacTask t = (BasicJavacTask) BasicJavacTask.instance(context);
 278             t.initPlugins(pluginOpts);
 279         }
 280 
 281         // init multi-release jar handling
 282         if (fileManager.isSupportedOption(Option.MULTIRELEASE.primaryName) == 1) {
 283             Target target = Target.instance(context);
 284             List<String> list = List.of(target.multiReleaseValue());
 285             fileManager.handleOption(Option.MULTIRELEASE.primaryName, list.iterator());
 286         }
 287 
 288         // init JavaCompiler
 289         JavaCompiler comp = JavaCompiler.instance(context);
 290 
 291         // init doclint
 292         List<String> docLintOpts = args.getDocLintOpts();
 293         if (!docLintOpts.isEmpty()) {
 294             BasicJavacTask t = (BasicJavacTask) BasicJavacTask.instance(context);
 295             t.initDocLint(docLintOpts);
 296         }
 297 
 298         if (options.get(Option.XSTDOUT) != null) {
 299             // Stdout reassigned - ask compiler to close it when it is done
 300             comp.closeables = comp.closeables.prepend(log.getWriter(WriterKind.NOTICE));
 301         }
 302 
 303         try {
 304             comp.compile(args.getFileObjects(), args.getClassNames(), null);
 305 
 306             if (log.expectDiagKeys != null) {
 307                 if (log.expectDiagKeys.isEmpty()) {
 308                     log.printRawLines("all expected diagnostics found");
 309                     return Result.OK;
 310                 } else {
 311                     log.printRawLines("expected diagnostic keys not found: " + log.expectDiagKeys);
 312                     return Result.ERROR;
 313                 }
 314             }
 315 
 316             return (comp.errorCount() == 0) ? Result.OK : Result.ERROR;
 317 
 318         } catch (OutOfMemoryError | StackOverflowError ex) {
 319             resourceMessage(ex);
 320             return Result.SYSERR;
 321         } catch (FatalError ex) {
 322             feMessage(ex, options);
 323             return Result.SYSERR;
 324         } catch (AnnotationProcessingError ex) {
 325             apMessage(ex);
 326             return Result.SYSERR;
 327         } catch (PropagatedException ex) {
 328             // TODO: what about errors from plugins?   should not simply rethrow the error here
 329             throw ex.getCause();
 330         } catch (Throwable ex) {
 331             // Nasty.  If we've already reported an error, compensate
 332             // for buggy compiler error recovery by swallowing thrown
 333             // exceptions.
 334             if (comp == null || comp.errorCount() == 0 || options.isSet("dev"))
 335                 bugMessage(ex);
 336             return Result.ABNORMAL;
 337         } finally {
 338             if (comp != null) {
 339                 try {
 340                     comp.close();
 341                 } catch (ClientCodeException ex) {
 342                     throw new RuntimeException(ex.getCause());
 343                 }
 344             }
 345         }
 346     }
 347 
 348     /** Print a message reporting an internal error.
 349      */
 350     void bugMessage(Throwable ex) {
 351         log.printLines(PrefixKind.JAVAC, "msg.bug", JavaCompiler.version());
 352         ex.printStackTrace(log.getWriter(WriterKind.NOTICE));
 353     }
 354 
 355     /** Print a message reporting a fatal error.
 356      */
 357     void feMessage(Throwable ex, Options options) {
 358         log.printRawLines(ex.getMessage());
 359         if (ex.getCause() != null && options.isSet("dev")) {
 360             ex.getCause().printStackTrace(log.getWriter(WriterKind.NOTICE));
 361         }
 362     }
 363 
 364     /** Print a message reporting an input/output error.
 365      */
 366     void ioMessage(Throwable ex) {
 367         log.printLines(PrefixKind.JAVAC, "msg.io");
 368         ex.printStackTrace(log.getWriter(WriterKind.NOTICE));
 369     }
 370 
 371     /** Print a message reporting an out-of-resources error.
 372      */
 373     void resourceMessage(Throwable ex) {
 374         log.printLines(PrefixKind.JAVAC, "msg.resource");
 375         ex.printStackTrace(log.getWriter(WriterKind.NOTICE));
 376     }
 377 
 378     /** Print a message reporting an uncaught exception from an
 379      * annotation processor.
 380      */
 381     void apMessage(AnnotationProcessingError ex) {
 382         log.printLines(PrefixKind.JAVAC, "msg.proc.annotation.uncaught.exception");
 383         ex.getCause().printStackTrace(log.getWriter(WriterKind.NOTICE));
 384     }
 385 
 386     /** Print a message reporting an uncaught exception from an
 387      * annotation processor.
 388      */
 389     void pluginMessage(Throwable ex) {
 390         log.printLines(PrefixKind.JAVAC, "msg.plugin.uncaught.exception");
 391         ex.printStackTrace(log.getWriter(WriterKind.NOTICE));
 392     }
 393 
 394     /** Display the location and checksum of a class. */
 395     void showClass(String className) {
 396         PrintWriter pw = log.getWriter(WriterKind.NOTICE);
 397         pw.println("javac: show class: " + className);
 398 
 399         URL url = getClass().getResource('/' + className.replace('.', '/') + ".class");
 400         if (url != null) {
 401             pw.println("  " + url);
 402         }
 403 
 404         try (InputStream in = getClass().getResourceAsStream('/' + className.replace('.', '/') + ".class")) {
 405             final String algorithm = "MD5";
 406             byte[] digest;
 407             MessageDigest md = MessageDigest.getInstance(algorithm);
 408             try (DigestInputStream din = new DigestInputStream(in, md)) {
 409                 byte[] buf = new byte[8192];
 410                 int n;
 411                 do { n = din.read(buf); } while (n > 0);
 412                 digest = md.digest();
 413             }
 414             StringBuilder sb = new StringBuilder();
 415             for (byte b: digest)
 416                 sb.append(String.format("%02x", b));
 417             pw.println("  " + algorithm + " checksum: " + sb);
 418         } catch (NoSuchAlgorithmException | IOException e) {
 419             pw.println("  cannot compute digest: " + e);
 420         }
 421     }
 422 
 423     // TODO: update this to JavacFileManager
 424     private JavaFileManager fileManager;
 425 
 426     /* ************************************************************************
 427      * Internationalization
 428      *************************************************************************/
 429 
 430     public static final String javacBundleName =
 431         "com.sun.tools.javac.resources.javac";
 432 }