1 /*
   2  * Copyright (c) 2014, 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 jdk.jshell;
  27 
  28 import com.sun.source.tree.CompilationUnitTree;
  29 import com.sun.source.tree.Tree;
  30 import com.sun.source.util.Trees;
  31 import com.sun.tools.javac.api.JavacTaskImpl;
  32 import com.sun.tools.javac.api.JavacTool;
  33 import com.sun.tools.javac.util.Context;
  34 import java.util.ArrayList;
  35 import java.util.Arrays;
  36 import java.util.List;
  37 import javax.tools.Diagnostic;
  38 import javax.tools.DiagnosticCollector;
  39 import javax.tools.JavaCompiler;
  40 import javax.tools.JavaFileManager;
  41 import javax.tools.JavaFileObject;
  42 import javax.tools.ToolProvider;
  43 import static jdk.jshell.Util.*;
  44 import com.sun.source.tree.ImportTree;
  45 import com.sun.tools.javac.code.Types;
  46 import com.sun.tools.javac.util.JavacMessages;
  47 import jdk.jshell.MemoryFileManager.OutputMemoryJavaFileObject;
  48 import java.util.Collections;
  49 import java.util.Locale;
  50 import static javax.tools.StandardLocation.CLASS_OUTPUT;
  51 import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
  52 import java.io.File;
  53 import java.util.Collection;
  54 import java.util.HashMap;
  55 import java.util.LinkedHashMap;
  56 import java.util.Map;
  57 import java.util.stream.Collectors;
  58 import static java.util.stream.Collectors.toList;
  59 import java.util.stream.Stream;
  60 import javax.lang.model.util.Elements;
  61 import javax.tools.FileObject;
  62 import jdk.jshell.MemoryFileManager.SourceMemoryJavaFileObject;
  63 import java.lang.Runtime.Version;
  64 
  65 /**
  66  * The primary interface to the compiler API.  Parsing, analysis, and
  67  * compilation to class files (in memory).
  68  * @author Robert Field
  69  */
  70 class TaskFactory {
  71 
  72     private final JavaCompiler compiler;
  73     private final MemoryFileManager fileManager;
  74     private final JShell state;
  75     private String classpath = System.getProperty("java.class.path");
  76     private final static Version INITIAL_SUPPORTED_VER = Version.parse("9");
  77 
  78     TaskFactory(JShell state) {
  79         this.state = state;
  80         this.compiler = ToolProvider.getSystemJavaCompiler();
  81         if (compiler == null) {
  82             throw new UnsupportedOperationException("Compiler not available, must be run with full JDK 9.");
  83         }
  84         Version current = Version.parse(System.getProperty("java.specification.version"));
  85         if (INITIAL_SUPPORTED_VER.compareToIgnoreOpt(current) > 0)  {
  86             throw new UnsupportedOperationException("Wrong compiler, must be run with full JDK 9.");
  87         }
  88         this.fileManager = new MemoryFileManager(
  89                 compiler.getStandardFileManager(null, null, null), state);
  90     }
  91 
  92     void addToClasspath(String path) {
  93         classpath = classpath + File.pathSeparator + path;
  94         List<String> args = new ArrayList<>();
  95         args.add(classpath);
  96         fileManager().handleOption("-classpath", args.iterator());
  97     }
  98 
  99     MemoryFileManager fileManager() {
 100         return fileManager;
 101     }
 102 
 103     private interface SourceHandler<T> {
 104 
 105         JavaFileObject sourceToFileObject(MemoryFileManager fm, T t);
 106 
 107         Diag diag(Diagnostic<? extends JavaFileObject> d);
 108     }
 109 
 110     private class StringSourceHandler implements SourceHandler<String> {
 111 
 112         @Override
 113         public JavaFileObject sourceToFileObject(MemoryFileManager fm, String src) {
 114             return fm.createSourceFileObject(src, "$NeverUsedName$", src);
 115         }
 116 
 117         @Override
 118         public Diag diag(final Diagnostic<? extends JavaFileObject> d) {
 119             return new Diag() {
 120 
 121                 @Override
 122                 public boolean isError() {
 123                     return d.getKind() == Diagnostic.Kind.ERROR;
 124                 }
 125 
 126                 @Override
 127                 public long getPosition() {
 128                     return d.getPosition();
 129                 }
 130 
 131                 @Override
 132                 public long getStartPosition() {
 133                     return d.getStartPosition();
 134                 }
 135 
 136                 @Override
 137                 public long getEndPosition() {
 138                     return d.getEndPosition();
 139                 }
 140 
 141                 @Override
 142                 public String getCode() {
 143                     return d.getCode();
 144                 }
 145 
 146                 @Override
 147                 public String getMessage(Locale locale) {
 148                     return expunge(d.getMessage(locale));
 149                 }
 150             };
 151         }
 152     }
 153 
 154     private class WrapSourceHandler implements SourceHandler<OuterWrap> {
 155 
 156         @Override
 157         public JavaFileObject sourceToFileObject(MemoryFileManager fm, OuterWrap w) {
 158             return fm.createSourceFileObject(w, w.classFullName(), w.wrapped());
 159         }
 160 
 161         @Override
 162         public Diag diag(Diagnostic<? extends JavaFileObject> d) {
 163             SourceMemoryJavaFileObject smjfo = (SourceMemoryJavaFileObject) d.getSource();
 164             OuterWrap w = (OuterWrap) smjfo.getOrigin();
 165             return w.wrapDiag(d);
 166         }
 167     }
 168 
 169     /**
 170      * Parse a snippet of code (as a String) using the parser subclass.  Return
 171      * the parse tree (and errors).
 172      */
 173     class ParseTask extends BaseTask {
 174 
 175         private final Iterable<? extends CompilationUnitTree> cuts;
 176         private final List<? extends Tree> units;
 177 
 178         ParseTask(final String source) {
 179             super(Stream.of(source),
 180                     new StringSourceHandler(),
 181                     "-XDallowStringFolding=false", "-proc:none");
 182             ReplParserFactory.instance(getContext());
 183             cuts = parse();
 184             units = Util.stream(cuts)
 185                     .flatMap(cut -> {
 186                         List<? extends ImportTree> imps = cut.getImports();
 187                         return (!imps.isEmpty() ? imps : cut.getTypeDecls()).stream();
 188                     })
 189                     .collect(toList());
 190         }
 191 
 192         private Iterable<? extends CompilationUnitTree> parse() {
 193             try {
 194                 return task.parse();
 195             } catch (Exception ex) {
 196                 throw new InternalError("Exception during parse - " + ex.getMessage(), ex);
 197             }
 198         }
 199 
 200         List<? extends Tree> units() {
 201             return units;
 202         }
 203 
 204         @Override
 205         Iterable<? extends CompilationUnitTree> cuTrees() {
 206             return cuts;
 207         }
 208     }
 209 
 210     /**
 211      * Run the normal "analyze()" pass of the compiler over the wrapped snippet.
 212      */
 213     class AnalyzeTask extends BaseTask {
 214 
 215         private final Iterable<? extends CompilationUnitTree> cuts;
 216 
 217         AnalyzeTask(final OuterWrap wrap, String... extraArgs) {
 218             this(Collections.singletonList(wrap), extraArgs);
 219         }
 220 
 221         AnalyzeTask(final Collection<OuterWrap> wraps, String... extraArgs) {
 222             this(wraps.stream(),
 223                     new WrapSourceHandler(),
 224                     Util.join(new String[] {
 225                         "-Xshouldstop:at=FLOW", "-Xlint:unchecked",
 226                         "-XaddExports:jdk.jshell/jdk.internal.jshell.remote=ALL-UNNAMED",
 227                         "-proc:none"
 228                     }, extraArgs));
 229         }
 230 
 231         private <T>AnalyzeTask(final Stream<T> stream, SourceHandler<T> sourceHandler,
 232                 String... extraOptions) {
 233             super(stream, sourceHandler, extraOptions);
 234             cuts = analyze();
 235         }
 236 
 237         private Iterable<? extends CompilationUnitTree> analyze() {
 238             try {
 239                 Iterable<? extends CompilationUnitTree> cuts = task.parse();
 240                 task.analyze();
 241                 return cuts;
 242             } catch (Exception ex) {
 243                 throw new InternalError("Exception during analyze - " + ex.getMessage(), ex);
 244             }
 245         }
 246 
 247         @Override
 248         Iterable<? extends CompilationUnitTree> cuTrees() {
 249             return cuts;
 250         }
 251 
 252         Elements getElements() {
 253             return task.getElements();
 254         }
 255 
 256         javax.lang.model.util.Types getTypes() {
 257             return task.getTypes();
 258         }
 259     }
 260 
 261     /**
 262      * Unit the wrapped snippet to class files.
 263      */
 264     class CompileTask extends BaseTask {
 265 
 266         private final Map<OuterWrap, List<OutputMemoryJavaFileObject>> classObjs = new HashMap<>();
 267 
 268         CompileTask(final Collection<OuterWrap> wraps) {
 269             super(wraps.stream(), new WrapSourceHandler(),
 270                     "-Xlint:unchecked", "-XaddExports:jdk.jshell/jdk.internal.jshell.remote=ALL-UNNAMED", "-proc:none", "-parameters");
 271         }
 272 
 273         boolean compile() {
 274             fileManager.registerClassFileCreationListener(this::listenForNewClassFile);
 275             boolean result = task.call();
 276             fileManager.registerClassFileCreationListener(null);
 277             return result;
 278         }
 279 
 280         // Returns the list of classes generated during this compile.
 281         // Stores the mapping between class name and current compiled bytes.
 282         List<String> classList(OuterWrap w) {
 283             List<OutputMemoryJavaFileObject> l = classObjs.get(w);
 284             if (l == null) {
 285                 return Collections.emptyList();
 286             }
 287             List<String> list = new ArrayList<>();
 288             for (OutputMemoryJavaFileObject fo : l) {
 289                 state.setClassnameToBytes(fo.getName(), fo.getBytes());
 290                 list.add(fo.getName());
 291             }
 292             return list;
 293         }
 294 
 295         private void listenForNewClassFile(OutputMemoryJavaFileObject jfo, JavaFileManager.Location location,
 296                 String className, JavaFileObject.Kind kind, FileObject sibling) {
 297             //debug("listenForNewClassFile %s loc=%s kind=%s\n", className, location, kind);
 298             if (location == CLASS_OUTPUT) {
 299                 state.debug(DBG_GEN, "Compiler generating class %s\n", className);
 300                 OuterWrap w = ((sibling instanceof SourceMemoryJavaFileObject)
 301                         && (((SourceMemoryJavaFileObject) sibling).getOrigin() instanceof OuterWrap))
 302                         ? (OuterWrap) ((SourceMemoryJavaFileObject) sibling).getOrigin()
 303                         : null;
 304                 classObjs.compute(w, (k, v) -> (v == null)? new ArrayList<>() : v)
 305                         .add(jfo);
 306             }
 307         }
 308 
 309         @Override
 310         Iterable<? extends CompilationUnitTree> cuTrees() {
 311             throw new UnsupportedOperationException("Not supported.");
 312         }
 313     }
 314 
 315     abstract class BaseTask {
 316 
 317         final DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
 318         final JavacTaskImpl task;
 319         private DiagList diags = null;
 320         private final SourceHandler<?> sourceHandler;
 321         private final Context context = new Context();
 322         private Types types;
 323         private JavacMessages messages;
 324         private Trees trees;
 325 
 326         private <T>BaseTask(Stream<T> inputs,
 327                 //BiFunction<MemoryFileManager, T, JavaFileObject> sfoCreator,
 328                 SourceHandler<T> sh,
 329                 String... extraOptions) {
 330             this.sourceHandler = sh;
 331             List<String> options = new ArrayList<>(extraOptions.length + state.extraCompilerOptions.size());
 332             options.addAll(Arrays.asList(extraOptions));
 333             options.addAll(state.extraCompilerOptions);
 334             Iterable<? extends JavaFileObject> compilationUnits = inputs
 335                             .map(in -> sh.sourceToFileObject(fileManager, in))
 336                             .collect(Collectors.toList());
 337             this.task = (JavacTaskImpl) ((JavacTool) compiler).getTask(null,
 338                     fileManager, diagnostics, options, null,
 339                     compilationUnits, context);
 340         }
 341 
 342         abstract Iterable<? extends CompilationUnitTree> cuTrees();
 343 
 344         CompilationUnitTree firstCuTree() {
 345             return cuTrees().iterator().next();
 346         }
 347 
 348         Diag diag(Diagnostic<? extends JavaFileObject> diag) {
 349             return sourceHandler.diag(diag);
 350         }
 351 
 352         Context getContext() {
 353             return context;
 354         }
 355 
 356         Types types() {
 357             if (types == null) {
 358                 types = Types.instance(context);
 359             }
 360             return types;
 361         }
 362 
 363         JavacMessages messages() {
 364             if (messages == null) {
 365                 messages = JavacMessages.instance(context);
 366             }
 367             return messages;
 368         }
 369 
 370         Trees trees() {
 371             if (trees == null) {
 372                 trees = Trees.instance(task);
 373             }
 374             return trees;
 375         }
 376 
 377         // ------------------ diags functionality
 378 
 379         DiagList getDiagnostics() {
 380             if (diags == null) {
 381                 LinkedHashMap<String, Diag> diagMap = new LinkedHashMap<>();
 382                 for (Diagnostic<? extends JavaFileObject> in : diagnostics.getDiagnostics()) {
 383                     Diag d = diag(in);
 384                     String uniqueKey = d.getCode() + ":" + d.getPosition() + ":" + d.getMessage(PARSED_LOCALE);
 385                     diagMap.put(uniqueKey, d);
 386                 }
 387                 diags = new DiagList(diagMap.values());
 388             }
 389             return diags;
 390         }
 391 
 392         boolean hasErrors() {
 393             return getDiagnostics().hasErrors();
 394         }
 395 
 396         String shortErrorMessage() {
 397             StringBuilder sb = new StringBuilder();
 398             for (Diag diag : getDiagnostics()) {
 399                 for (String line : diag.getMessage(PARSED_LOCALE).split("\\r?\\n")) {
 400                     if (!line.trim().startsWith("location:")) {
 401                         sb.append(line);
 402                     }
 403                 }
 404             }
 405             return sb.toString();
 406         }
 407 
 408         void debugPrintDiagnostics(String src) {
 409             for (Diag diag : getDiagnostics()) {
 410                 state.debug(DBG_GEN, "ERROR --\n");
 411                 for (String line : diag.getMessage(PARSED_LOCALE).split("\\r?\\n")) {
 412                     if (!line.trim().startsWith("location:")) {
 413                         state.debug(DBG_GEN, "%s\n", line);
 414                     }
 415                 }
 416                 int start = (int) diag.getStartPosition();
 417                 int end = (int) diag.getEndPosition();
 418                 if (src != null) {
 419                     String[] srcLines = src.split("\\r?\\n");
 420                     for (String line : srcLines) {
 421                         state.debug(DBG_GEN, "%s\n", line);
 422                     }
 423 
 424                     StringBuilder sb = new StringBuilder();
 425                     for (int i = 0; i < start; ++i) {
 426                         sb.append(' ');
 427                     }
 428                     sb.append('^');
 429                     if (end > start) {
 430                         for (int i = start + 1; i < end; ++i) {
 431                             sb.append('-');
 432                         }
 433                         sb.append('^');
 434                     }
 435                     state.debug(DBG_GEN, "%s\n", sb.toString());
 436                 }
 437                 state.debug(DBG_GEN, "printDiagnostics start-pos = %d ==> %d -- wrap = %s\n",
 438                         diag.getStartPosition(), start, this);
 439                 state.debug(DBG_GEN, "Code: %s\n", diag.getCode());
 440                 state.debug(DBG_GEN, "Pos: %d (%d - %d) -- %s\n", diag.getPosition(),
 441                         diag.getStartPosition(), diag.getEndPosition(), diag.getMessage(null));
 442             }
 443         }
 444     }
 445 
 446 }