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 package jdk.jshell;
  26 
  27 import java.util.ArrayList;
  28 import java.util.Collection;
  29 import java.util.Collections;
  30 import java.util.List;
  31 import java.util.Locale;
  32 import java.util.regex.Matcher;
  33 import java.util.regex.Pattern;
  34 import java.util.stream.Collectors;
  35 import javax.lang.model.element.Modifier;
  36 import com.sun.source.tree.ArrayTypeTree;
  37 import com.sun.source.tree.AssignmentTree;
  38 import com.sun.source.tree.ClassTree;
  39 import com.sun.source.tree.ExpressionTree;
  40 import com.sun.source.tree.IdentifierTree;
  41 import com.sun.source.tree.MethodTree;
  42 import com.sun.source.tree.ModifiersTree;
  43 import com.sun.source.tree.Tree;
  44 import com.sun.source.tree.VariableTree;
  45 import com.sun.tools.javac.tree.JCTree;
  46 import com.sun.tools.javac.tree.Pretty;
  47 import java.io.IOException;
  48 import java.io.StringWriter;
  49 import java.io.Writer;
  50 import java.util.LinkedHashSet;
  51 import java.util.Set;
  52 import jdk.jshell.Key.ErroneousKey;
  53 import jdk.jshell.Key.MethodKey;
  54 import jdk.jshell.Key.TypeDeclKey;
  55 import jdk.jshell.Snippet.Kind;
  56 import jdk.jshell.Snippet.SubKind;
  57 import jdk.jshell.TaskFactory.AnalyzeTask;
  58 import jdk.jshell.TaskFactory.BaseTask;
  59 import jdk.jshell.TaskFactory.CompileTask;
  60 import jdk.jshell.TaskFactory.ParseTask;
  61 import jdk.jshell.TreeDissector.ExpressionInfo;
  62 import jdk.jshell.Wrap.Range;
  63 import jdk.jshell.Snippet.Status;
  64 import static java.util.stream.Collectors.toList;
  65 import static java.util.stream.Collectors.toSet;
  66 import static java.util.Collections.singletonList;
  67 import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
  68 import static jdk.jshell.Util.DOIT_METHOD_NAME;
  69 import static jdk.jshell.Util.PREFIX_PATTERN;
  70 import static jdk.jshell.Util.expunge;
  71 import static jdk.jshell.Snippet.SubKind.SINGLE_TYPE_IMPORT_SUBKIND;
  72 import static jdk.jshell.Snippet.SubKind.SINGLE_STATIC_IMPORT_SUBKIND;
  73 import static jdk.jshell.Snippet.SubKind.TYPE_IMPORT_ON_DEMAND_SUBKIND;
  74 import static jdk.jshell.Snippet.SubKind.STATIC_IMPORT_ON_DEMAND_SUBKIND;
  75 
  76 /**
  77  * The Evaluation Engine. Source internal analysis, wrapping control,
  78  * compilation, declaration. redefinition, replacement, and execution.
  79  *
  80  * @author Robert Field
  81  */
  82 class Eval {
  83 
  84     private static final Pattern IMPORT_PATTERN = Pattern.compile("import\\p{javaWhitespace}+(?<static>static\\p{javaWhitespace}+)?(?<fullname>[\\p{L}\\p{N}_\\$\\.]+\\.(?<name>[\\p{L}\\p{N}_\\$]+|\\*))");
  85 
  86     private int varNumber = 0;
  87 
  88     private final JShell state;
  89 
  90     Eval(JShell state) {
  91         this.state = state;
  92     }
  93 
  94     /**
  95      * Evaluates a snippet of source.
  96      *
  97      * @param userSource the source of the snippet
  98      * @return the list of primary and update events
  99      * @throws IllegalStateException
 100      */
 101     List<SnippetEvent> eval(String userSource) throws IllegalStateException {
 102         List<SnippetEvent> allEvents = new ArrayList<>();
 103         for (Snippet snip : sourceToSnippets(userSource)) {
 104             if (snip.kind() == Kind.ERRONEOUS) {
 105                 state.maps.installSnippet(snip);
 106                 allEvents.add(new SnippetEvent(
 107                         snip, Status.NONEXISTENT, Status.REJECTED,
 108                         false, null, null, null));
 109             } else {
 110                 allEvents.addAll(declare(snip, snip.syntheticDiags()));
 111             }
 112         }
 113         return allEvents;
 114     }
 115 
 116     /**
 117      * Converts the user source of a snippet into a Snippet list -- Snippet will
 118      * have wrappers.
 119      *
 120      * @param userSource the source of the snippet
 121      * @return usually a singleton list of Snippet, but may be empty or multiple
 122      */
 123     List<Snippet> sourceToSnippetsWithWrappers(String userSource) {
 124         List<Snippet> snippets = sourceToSnippets(userSource);
 125         for (Snippet snip : snippets) {
 126             if (snip.outerWrap() == null) {
 127                 snip.setOuterWrap(
 128                         (snip.kind() == Kind.IMPORT)
 129                                 ? state.outerMap.wrapImport(snip.guts(), snip)
 130                                 : state.outerMap.wrapInTrialClass(snip.guts())
 131                 );
 132             }
 133         }
 134         return snippets;
 135     }
 136 
 137     /**
 138      * Converts the user source of a snippet into a Snippet object (or list of
 139      * objects in the case of: int x, y, z;).  Does not install the Snippets
 140      * or execute them.
 141      *
 142      * @param userSource the source of the snippet
 143      * @return usually a singleton list of Snippet, but may be empty or multiple
 144      */
 145     private List<Snippet> sourceToSnippets(String userSource) {
 146         String compileSource = Util.trimEnd(new MaskCommentsAndModifiers(userSource, false).cleared());
 147         if (compileSource.length() == 0) {
 148             return Collections.emptyList();
 149         }
 150         ParseTask pt = state.taskFactory.new ParseTask(compileSource);
 151         List<? extends Tree> units = pt.units();
 152         if (units.isEmpty()) {
 153             return compileFailResult(pt, userSource, Kind.ERRONEOUS);
 154         }
 155         Tree unitTree = units.get(0);
 156         if (pt.getDiagnostics().hasOtherThanNotStatementErrors()) {
 157             return compileFailResult(pt, userSource, kindOfTree(unitTree));
 158         }
 159 
 160         // Erase illegal/ignored modifiers
 161         compileSource = new MaskCommentsAndModifiers(compileSource, true).cleared();
 162 
 163         state.debug(DBG_GEN, "Kind: %s -- %s\n", unitTree.getKind(), unitTree);
 164         switch (unitTree.getKind()) {
 165             case IMPORT:
 166                 return processImport(userSource, compileSource);
 167             case VARIABLE:
 168                 return processVariables(userSource, units, compileSource, pt);
 169             case EXPRESSION_STATEMENT:
 170                 return processExpression(userSource, compileSource);
 171             case CLASS:
 172                 return processClass(userSource, unitTree, compileSource, SubKind.CLASS_SUBKIND, pt);
 173             case ENUM:
 174                 return processClass(userSource, unitTree, compileSource, SubKind.ENUM_SUBKIND, pt);
 175             case ANNOTATION_TYPE:
 176                 return processClass(userSource, unitTree, compileSource, SubKind.ANNOTATION_TYPE_SUBKIND, pt);
 177             case INTERFACE:
 178                 return processClass(userSource, unitTree, compileSource, SubKind.INTERFACE_SUBKIND, pt);
 179             case METHOD:
 180                 return processMethod(userSource, unitTree, compileSource, pt);
 181             default:
 182                 return processStatement(userSource, compileSource);
 183         }
 184     }
 185 
 186     private List<Snippet> processImport(String userSource, String compileSource) {
 187         Wrap guts = Wrap.simpleWrap(compileSource);
 188         Matcher mat = IMPORT_PATTERN.matcher(compileSource);
 189         String fullname;
 190         String name;
 191         boolean isStatic;
 192         if (mat.find()) {
 193             isStatic = mat.group("static") != null;
 194             name = mat.group("name");
 195             fullname = mat.group("fullname");
 196         } else {
 197             // bad import -- fake it
 198             isStatic = compileSource.contains("static");
 199             name = fullname = compileSource;
 200         }
 201         String fullkey = (isStatic ? "static-" : "") + fullname;
 202         boolean isStar = name.equals("*");
 203         String keyName = isStar
 204                 ? fullname
 205                 : name;
 206         SubKind snippetKind = isStar
 207                 ? (isStatic ? STATIC_IMPORT_ON_DEMAND_SUBKIND : TYPE_IMPORT_ON_DEMAND_SUBKIND)
 208                 : (isStatic ? SINGLE_STATIC_IMPORT_SUBKIND : SINGLE_TYPE_IMPORT_SUBKIND);
 209         Snippet snip = new ImportSnippet(state.keyMap.keyForImport(keyName, snippetKind),
 210                 userSource, guts, fullname, name, snippetKind, fullkey, isStatic, isStar);
 211         return singletonList(snip);
 212     }
 213 
 214     private static class EvalPretty extends Pretty {
 215 
 216         private final Writer out;
 217 
 218         public EvalPretty(Writer writer, boolean bln) {
 219             super(writer, bln);
 220             this.out = writer;
 221         }
 222 
 223         /**
 224          * Print string, DO NOT replacing all non-ascii character with unicode
 225          * escapes.
 226          */
 227         @Override
 228         public void print(Object o) throws IOException {
 229             out.write(o.toString());
 230         }
 231 
 232         static String prettyExpr(JCTree tree, boolean bln) {
 233             StringWriter out = new StringWriter();
 234             try {
 235                 new EvalPretty(out, bln).printExpr(tree);
 236             } catch (IOException e) {
 237                 throw new AssertionError(e);
 238             }
 239             return out.toString();
 240         }
 241     }
 242 
 243     private List<Snippet> processVariables(String userSource, List<? extends Tree> units, String compileSource, ParseTask pt) {
 244         List<Snippet> snippets = new ArrayList<>();
 245         TreeDissector dis = TreeDissector.createByFirstClass(pt);
 246         for (Tree unitTree : units) {
 247             VariableTree vt = (VariableTree) unitTree;
 248             String name = vt.getName().toString();
 249             String typeName = EvalPretty.prettyExpr((JCTree) vt.getType(), false);
 250             Tree baseType = vt.getType();
 251             TreeDependencyScanner tds = new TreeDependencyScanner();
 252             tds.scan(baseType); // Not dependent on initializer
 253             StringBuilder sbBrackets = new StringBuilder();
 254             while (baseType instanceof ArrayTypeTree) {
 255                 //TODO handle annotations too
 256                 baseType = ((ArrayTypeTree) baseType).getType();
 257                 sbBrackets.append("[]");
 258             }
 259             Range rtype = dis.treeToRange(baseType);
 260             Range runit = dis.treeToRange(vt);
 261             runit = new Range(runit.begin, runit.end - 1);
 262             ExpressionTree it = vt.getInitializer();
 263             Range rinit = null;
 264             int nameMax = runit.end - 1;
 265             SubKind subkind;
 266             if (it != null) {
 267                 subkind = SubKind.VAR_DECLARATION_WITH_INITIALIZER_SUBKIND;
 268                 rinit = dis.treeToRange(it);
 269                 nameMax = rinit.begin - 1;
 270             } else {
 271                 subkind = SubKind.VAR_DECLARATION_SUBKIND;
 272             }
 273             int nameStart = compileSource.lastIndexOf(name, nameMax);
 274             if (nameStart < 0) {
 275                 throw new AssertionError("Name '" + name + "' not found");
 276             }
 277             int nameEnd = nameStart + name.length();
 278             Range rname = new Range(nameStart, nameEnd);
 279             Wrap guts = Wrap.varWrap(compileSource, rtype, sbBrackets.toString(), rname, rinit);
 280             DiagList modDiag = modifierDiagnostics(vt.getModifiers(), dis, true);
 281             Snippet snip = new VarSnippet(state.keyMap.keyForVariable(name), userSource, guts,
 282                     name, subkind, typeName,
 283                     tds.declareReferences(), modDiag);
 284             snippets.add(snip);
 285         }
 286         return snippets;
 287     }
 288 
 289     private List<Snippet> processExpression(String userSource, String compileSource) {
 290         String name = null;
 291         ExpressionInfo ei = typeOfExpression(compileSource);
 292         ExpressionTree assignVar;
 293         Wrap guts;
 294         Snippet snip;
 295         if (ei != null && ei.isNonVoid) {
 296             String typeName = ei.typeName;
 297             SubKind subkind;
 298             if (ei.tree instanceof IdentifierTree) {
 299                 IdentifierTree id = (IdentifierTree) ei.tree;
 300                 name = id.getName().toString();
 301                 subkind = SubKind.VAR_VALUE_SUBKIND;
 302 
 303             } else if (ei.tree instanceof AssignmentTree
 304                     && (assignVar = ((AssignmentTree) ei.tree).getVariable()) instanceof IdentifierTree) {
 305                 name = assignVar.toString();
 306                 subkind = SubKind.ASSIGNMENT_SUBKIND;
 307             } else {
 308                 subkind = SubKind.OTHER_EXPRESSION_SUBKIND;
 309             }
 310             if (shouldGenTempVar(subkind)) {
 311                 if (state.tempVariableNameGenerator != null) {
 312                     name = state.tempVariableNameGenerator.get();
 313                 }
 314                 while (name == null || state.keyMap.doesVariableNameExist(name)) {
 315                     name = "$" + ++varNumber;
 316                 }
 317                 guts = Wrap.tempVarWrap(compileSource, typeName, name);
 318                 Collection<String> declareReferences = null; //TODO
 319                 snip = new VarSnippet(state.keyMap.keyForVariable(name), userSource, guts,
 320                         name, SubKind.TEMP_VAR_EXPRESSION_SUBKIND, typeName, declareReferences, null);
 321             } else {
 322                 guts = Wrap.methodReturnWrap(compileSource);
 323                 snip = new ExpressionSnippet(state.keyMap.keyForExpression(name, typeName), userSource, guts,
 324                         name, subkind);
 325             }
 326         } else {
 327             guts = Wrap.methodWrap(compileSource);
 328             if (ei == null) {
 329                 // We got no type info, check for not a statement by trying
 330                 AnalyzeTask at = trialCompile(guts);
 331                 if (at.getDiagnostics().hasNotStatement()) {
 332                     guts = Wrap.methodReturnWrap(compileSource);
 333                     at = trialCompile(guts);
 334                 }
 335                 if (at.hasErrors()) {
 336                     return compileFailResult(at, userSource, Kind.EXPRESSION);
 337                 }
 338             }
 339             snip = new StatementSnippet(state.keyMap.keyForStatement(), userSource, guts);
 340         }
 341         return singletonList(snip);
 342     }
 343 
 344     private List<Snippet> processClass(String userSource, Tree unitTree, String compileSource, SubKind snippetKind, ParseTask pt) {
 345         TreeDependencyScanner tds = new TreeDependencyScanner();
 346         tds.scan(unitTree);
 347 
 348         TreeDissector dis = TreeDissector.createByFirstClass(pt);
 349 
 350         ClassTree klassTree = (ClassTree) unitTree;
 351         String name = klassTree.getSimpleName().toString();
 352         DiagList modDiag = modifierDiagnostics(klassTree.getModifiers(), dis, false);
 353         TypeDeclKey key = state.keyMap.keyForClass(name);
 354         // Corralling mutates.  Must be last use of pt, unitTree, klassTree
 355         Wrap corralled = new Corraller(key.index(), pt.getContext()).corralType(klassTree);
 356 
 357         Wrap guts = Wrap.classMemberWrap(compileSource);
 358         Snippet snip = new TypeDeclSnippet(key, userSource, guts,
 359                 name, snippetKind,
 360                 corralled, tds.declareReferences(), tds.bodyReferences(), modDiag);
 361         return singletonList(snip);
 362     }
 363 
 364     private List<Snippet> processStatement(String userSource, String compileSource) {
 365         Wrap guts = Wrap.methodWrap(compileSource);
 366         // Check for unreachable by trying
 367         AnalyzeTask at = trialCompile(guts);
 368         if (at.hasErrors()) {
 369             if (at.getDiagnostics().hasUnreachableError()) {
 370                 guts = Wrap.methodUnreachableSemiWrap(compileSource);
 371                 at = trialCompile(guts);
 372                 if (at.hasErrors()) {
 373                     if (at.getDiagnostics().hasUnreachableError()) {
 374                         // Without ending semicolon
 375                         guts = Wrap.methodUnreachableWrap(compileSource);
 376                         at = trialCompile(guts);
 377                     }
 378                     if (at.hasErrors()) {
 379                         return compileFailResult(at, userSource, Kind.STATEMENT);
 380                     }
 381                 }
 382             } else {
 383                 return compileFailResult(at, userSource, Kind.STATEMENT);
 384             }
 385         }
 386         Snippet snip = new StatementSnippet(state.keyMap.keyForStatement(), userSource, guts);
 387         return singletonList(snip);
 388     }
 389 
 390     private AnalyzeTask trialCompile(Wrap guts) {
 391         OuterWrap outer = state.outerMap.wrapInTrialClass(guts);
 392         return state.taskFactory.new AnalyzeTask(outer);
 393     }
 394 
 395     private List<Snippet> processMethod(String userSource, Tree unitTree, String compileSource, ParseTask pt) {
 396         TreeDependencyScanner tds = new TreeDependencyScanner();
 397         tds.scan(unitTree);
 398         TreeDissector dis = TreeDissector.createByFirstClass(pt);
 399 
 400         MethodTree mt = (MethodTree) unitTree;
 401         String name = mt.getName().toString();
 402         String parameterTypes
 403                 = mt.getParameters()
 404                 .stream()
 405                 .map(param -> dis.treeToRange(param.getType()).part(compileSource))
 406                 .collect(Collectors.joining(","));
 407         Tree returnType = mt.getReturnType();
 408         DiagList modDiag = modifierDiagnostics(mt.getModifiers(), dis, true);
 409         MethodKey key = state.keyMap.keyForMethod(name, parameterTypes);
 410         // Corralling mutates.  Must be last use of pt, unitTree, mt
 411         Wrap corralled = new Corraller(key.index(), pt.getContext()).corralMethod(mt);
 412 
 413         if (modDiag.hasErrors()) {
 414             return compileFailResult(modDiag, userSource, Kind.METHOD);
 415         }
 416         Wrap guts = Wrap.classMemberWrap(compileSource);
 417         Range typeRange = dis.treeToRange(returnType);
 418         String signature = "(" + parameterTypes + ")" + typeRange.part(compileSource);
 419 
 420         Snippet snip = new MethodSnippet(key, userSource, guts,
 421                 name, signature,
 422                 corralled, tds.declareReferences(), tds.bodyReferences(), modDiag);
 423         return singletonList(snip);
 424     }
 425 
 426     private Kind kindOfTree(Tree tree) {
 427         switch (tree.getKind()) {
 428             case IMPORT:
 429                 return Kind.IMPORT;
 430             case VARIABLE:
 431                 return Kind.VAR;
 432             case EXPRESSION_STATEMENT:
 433                 return Kind.EXPRESSION;
 434             case CLASS:
 435             case ENUM:
 436             case ANNOTATION_TYPE:
 437             case INTERFACE:
 438                 return Kind.TYPE_DECL;
 439             case METHOD:
 440                 return Kind.METHOD;
 441             default:
 442                 return Kind.STATEMENT;
 443         }
 444     }
 445 
 446     /**
 447      * The snippet has failed, return with the rejected snippet
 448      *
 449      * @param xt the task from which to extract the failure diagnostics
 450      * @param userSource the incoming bad user source
 451      * @return a rejected snippet
 452      */
 453     private List<Snippet> compileFailResult(BaseTask xt, String userSource, Kind probableKind) {
 454         return compileFailResult(xt.getDiagnostics(), userSource, probableKind);
 455     }
 456 
 457     /**
 458      * The snippet has failed, return with the rejected snippet
 459      *
 460      * @param diags the failure diagnostics
 461      * @param userSource the incoming bad user source
 462      * @return a rejected snippet
 463      */
 464     private List<Snippet> compileFailResult(DiagList diags, String userSource, Kind probableKind) {
 465         ErroneousKey key = state.keyMap.keyForErroneous();
 466         Snippet snip = new ErroneousSnippet(key, userSource, null,
 467                 probableKind, SubKind.UNKNOWN_SUBKIND);
 468         snip.setFailed(diags);
 469 
 470         // Install  wrapper for query by SourceCodeAnalysis.wrapper
 471         String compileSource = Util.trimEnd(new MaskCommentsAndModifiers(userSource, true).cleared());
 472         OuterWrap outer;
 473         switch (probableKind) {
 474             case IMPORT:
 475                 outer = state.outerMap.wrapImport(Wrap.simpleWrap(compileSource), snip);
 476                 break;
 477             case EXPRESSION:
 478                 outer = state.outerMap.wrapInTrialClass(Wrap.methodReturnWrap(compileSource));
 479                 break;
 480             case VAR:
 481             case TYPE_DECL:
 482             case METHOD:
 483                 outer = state.outerMap.wrapInTrialClass(Wrap.classMemberWrap(compileSource));
 484                 break;
 485             default:
 486                 outer = state.outerMap.wrapInTrialClass(Wrap.methodWrap(compileSource));
 487                 break;
 488         }
 489         snip.setOuterWrap(outer);
 490 
 491         return singletonList(snip);
 492     }
 493 
 494     private ExpressionInfo typeOfExpression(String expression) {
 495         Wrap guts = Wrap.methodReturnWrap(expression);
 496         TaskFactory.AnalyzeTask at = trialCompile(guts);
 497         if (!at.hasErrors() && at.firstCuTree() != null) {
 498             return TreeDissector.createByFirstClass(at)
 499                     .typeOfReturnStatement(at, state);
 500         }
 501         return null;
 502     }
 503 
 504     /**
 505      * Should a temp var wrap the expression. TODO make this user configurable.
 506      *
 507      * @param snippetKind
 508      * @return
 509      */
 510     private boolean shouldGenTempVar(SubKind snippetKind) {
 511         return snippetKind == SubKind.OTHER_EXPRESSION_SUBKIND;
 512     }
 513 
 514     List<SnippetEvent> drop(Snippet si) {
 515         Unit c = new Unit(state, si);
 516 
 517         Set<Unit> ins = c.dependents().collect(toSet());
 518         Set<Unit> outs = compileAndLoad(ins);
 519 
 520         return events(c, outs, null, null);
 521     }
 522 
 523     private List<SnippetEvent> declare(Snippet si, DiagList generatedDiagnostics) {
 524         Unit c = new Unit(state, si, null, generatedDiagnostics);
 525         Set<Unit> ins = new LinkedHashSet<>();
 526         ins.add(c);
 527         Set<Unit> outs = compileAndLoad(ins);
 528 
 529         if (!si.status().isDefined()
 530                 && si.diagnostics().isEmpty()
 531                 && si.unresolved().isEmpty()) {
 532             // did not succeed, but no record of it, extract from others
 533             si.setDiagnostics(outs.stream()
 534                     .flatMap(u -> u.snippet().diagnostics().stream())
 535                     .collect(Collectors.toCollection(DiagList::new)));
 536         }
 537 
 538         // If appropriate, execute the snippet
 539         String value = null;
 540         JShellException exception = null;
 541         if (si.status().isDefined()) {
 542             if (si.isExecutable()) {
 543                 try {
 544                 value = state.executionControl().invoke(si.classFullName(), DOIT_METHOD_NAME);
 545                     value = si.subKind().hasValue()
 546                             ? expunge(value)
 547                             : "";
 548                 } catch (EvalException ex) {
 549                     exception = translateExecutionException(ex);
 550                 } catch (JShellException ex) {
 551                     // UnresolvedReferenceException
 552                     exception = ex;
 553                 }
 554             } else if (si.subKind() == SubKind.VAR_DECLARATION_SUBKIND) {
 555                 switch (((VarSnippet) si).typeName()) {
 556                     case "byte":
 557                     case "short":
 558                     case "int":
 559                     case "long":
 560                         value = "0";
 561                         break;
 562                     case "float":
 563                     case "double":
 564                         value = "0.0";
 565                         break;
 566                     case "boolean":
 567                         value = "false";
 568                         break;
 569                     case "char":
 570                         value = "''";
 571                         break;
 572                     default:
 573                         value = "null";
 574                         break;
 575                 }
 576             }
 577         }
 578         return events(c, outs, value, exception);
 579     }
 580 
 581     private boolean interestingEvent(SnippetEvent e) {
 582         return e.isSignatureChange()
 583                     || e.causeSnippet() == null
 584                     || e.status() != e.previousStatus()
 585                     || e.exception() != null;
 586     }
 587 
 588     private List<SnippetEvent> events(Unit c, Collection<Unit> outs, String value, JShellException exception) {
 589         List<SnippetEvent> events = new ArrayList<>();
 590         events.add(c.event(value, exception));
 591         events.addAll(outs.stream()
 592                 .filter(u -> u != c)
 593                 .map(u -> u.event(null, null))
 594                 .filter(this::interestingEvent)
 595                 .collect(Collectors.toList()));
 596         events.addAll(outs.stream()
 597                 .flatMap(u -> u.secondaryEvents().stream())
 598                 .filter(this::interestingEvent)
 599                 .collect(Collectors.toList()));
 600         //System.err.printf("Events: %s\n", events);
 601         return events;
 602     }
 603 
 604     private Set<OuterWrap> outerWrapSet(Collection<Unit> units) {
 605         return units.stream()
 606                 .map(u -> u.snippet().outerWrap())
 607                 .collect(toSet());
 608     }
 609 
 610     private Set<Unit> compileAndLoad(Set<Unit> ins) {
 611         if (ins.isEmpty()) {
 612             return ins;
 613         }
 614         Set<Unit> replaced = new LinkedHashSet<>();
 615         // Loop until dependencies and errors are stable
 616         while (true) {
 617             state.debug(DBG_GEN, "compileAndLoad  %s\n", ins);
 618 
 619             ins.stream().forEach(u -> u.initialize());
 620             ins.stream().forEach(u -> u.setWrap(ins, ins));
 621             AnalyzeTask at = state.taskFactory.new AnalyzeTask(outerWrapSet(ins));
 622             ins.stream().forEach(u -> u.setDiagnostics(at));
 623 
 624             // corral any Snippets that need it
 625             AnalyzeTask cat;
 626             if (ins.stream().anyMatch(u -> u.corralIfNeeded(ins))) {
 627                 // if any were corralled, re-analyze everything
 628                 cat = state.taskFactory.new AnalyzeTask(outerWrapSet(ins));
 629                 ins.stream().forEach(u -> u.setCorralledDiagnostics(cat));
 630             } else {
 631                 cat = at;
 632             }
 633             ins.stream().forEach(u -> u.setStatus(cat));
 634             // compile and load the legit snippets
 635             boolean success;
 636             while (true) {
 637                 List<Unit> legit = ins.stream()
 638                         .filter(u -> u.isDefined())
 639                         .collect(toList());
 640                 state.debug(DBG_GEN, "compileAndLoad ins = %s -- legit = %s\n",
 641                         ins, legit);
 642                 if (legit.isEmpty()) {
 643                     // no class files can be generated
 644                     success = true;
 645                 } else {
 646                     // re-wrap with legit imports
 647                     legit.stream().forEach(u -> u.setWrap(ins, legit));
 648 
 649                     // generate class files for those capable
 650                     CompileTask ct = state.taskFactory.new CompileTask(outerWrapSet(legit));
 651                     if (!ct.compile()) {
 652                         // oy! compile failed because of recursive new unresolved
 653                         if (legit.stream()
 654                                 .filter(u -> u.smashingErrorDiagnostics(ct))
 655                                 .count() > 0) {
 656                             // try again, with the erroreous removed
 657                             continue;
 658                         } else {
 659                             state.debug(DBG_GEN, "Should never happen error-less failure - %s\n",
 660                                     legit);
 661                         }
 662                     }
 663 
 664                     // load all new classes
 665                     load(legit.stream()
 666                             .flatMap(u -> u.classesToLoad(ct.classList(u.snippet().outerWrap())))
 667                             .collect(toSet()));
 668                     // attempt to redefine the remaining classes
 669                     List<Unit> toReplace = legit.stream()
 670                             .filter(u -> !u.doRedefines())
 671                             .collect(toList());
 672 
 673                     // prevent alternating redefine/replace cyclic dependency
 674                     // loop by replacing all that have been replaced
 675                     if (!toReplace.isEmpty()) {
 676                         replaced.addAll(toReplace);
 677                         replaced.stream().forEach(u -> u.markForReplacement());
 678                     }
 679 
 680                     success = toReplace.isEmpty();
 681                 }
 682                 break;
 683             }
 684 
 685             // add any new dependencies to the working set
 686             List<Unit> newDependencies = ins.stream()
 687                     .flatMap(u -> u.effectedDependents())
 688                     .collect(toList());
 689             state.debug(DBG_GEN, "compileAndLoad %s -- deps: %s  success: %s\n",
 690                     ins, newDependencies, success);
 691             if (!ins.addAll(newDependencies) && success) {
 692                 // all classes that could not be directly loaded (because they
 693                 // are new) have been redefined, and no new dependnencies were
 694                 // identified
 695                 ins.stream().forEach(u -> u.finish());
 696                 return ins;
 697             }
 698         }
 699     }
 700 
 701     /**
 702      * If there are classes to load, loads by calling the execution engine.
 703      * @param classnames names of the classes to load.
 704      */
 705     private void load(Collection<String> classnames) {
 706         if (!classnames.isEmpty()) {
 707             state.executionControl().load(classnames);
 708         }
 709     }
 710 
 711     private EvalException translateExecutionException(EvalException ex) {
 712         StackTraceElement[] raw = ex.getStackTrace();
 713         int last = raw.length;
 714         do {
 715             if (last == 0) {
 716                 last = raw.length - 1;
 717                 break;
 718             }
 719         } while (!isWrap(raw[--last]));
 720         StackTraceElement[] elems = new StackTraceElement[last + 1];
 721         for (int i = 0; i <= last; ++i) {
 722             StackTraceElement r = raw[i];
 723             OuterSnippetsClassWrap outer = state.outerMap.getOuter(r.getClassName());
 724             if (outer != null) {
 725                 String klass = expunge(r.getClassName());
 726                 String method = r.getMethodName().equals(DOIT_METHOD_NAME) ? "" : r.getMethodName();
 727                 int wln = r.getLineNumber() - 1;
 728                 int line = outer.wrapLineToSnippetLine(wln) + 1;
 729                 Snippet sn = outer.wrapLineToSnippet(wln);
 730                 String file = "#" + sn.id();
 731                 elems[i] = new StackTraceElement(klass, method, file, line);
 732             } else if (r.getFileName().equals("<none>")) {
 733                 elems[i] = new StackTraceElement(r.getClassName(), r.getMethodName(), null, r.getLineNumber());
 734             } else {
 735                 elems[i] = r;
 736             }
 737         }
 738         String msg = ex.getMessage();
 739         if (msg.equals("<none>")) {
 740             msg = null;
 741         }
 742         return new EvalException(msg, ex.getExceptionClassName(), elems);
 743     }
 744 
 745     private boolean isWrap(StackTraceElement ste) {
 746         return PREFIX_PATTERN.matcher(ste.getClassName()).find();
 747     }
 748 
 749     private DiagList modifierDiagnostics(ModifiersTree modtree,
 750             final TreeDissector dis, boolean isAbstractProhibited) {
 751 
 752         class ModifierDiagnostic extends Diag {
 753 
 754             final boolean fatal;
 755             final String message;
 756 
 757             ModifierDiagnostic(List<Modifier> list, boolean fatal) {
 758                 this.fatal = fatal;
 759                 StringBuilder sb = new StringBuilder();
 760                 for (Modifier mod : list) {
 761                     sb.append("'");
 762                     sb.append(mod.toString());
 763                     sb.append("' ");
 764                 }
 765                 String key = (list.size() > 1)
 766                         ? fatal
 767                             ? "jshell.diag.modifier.plural.fatal"
 768                             : "jshell.diag.modifier.plural.ignore"
 769                         : fatal
 770                             ? "jshell.diag.modifier.single.fatal"
 771                             : "jshell.diag.modifier.single.ignore";
 772                 this.message = state.messageFormat(key, sb.toString());
 773             }
 774 
 775             @Override
 776             public boolean isError() {
 777                 return fatal;
 778             }
 779 
 780             @Override
 781             public long getPosition() {
 782                 return dis.getStartPosition(modtree);
 783             }
 784 
 785             @Override
 786             public long getStartPosition() {
 787                 return dis.getStartPosition(modtree);
 788             }
 789 
 790             @Override
 791             public long getEndPosition() {
 792                 return dis.getEndPosition(modtree);
 793             }
 794 
 795             @Override
 796             public String getCode() {
 797                 return fatal
 798                         ? "jdk.eval.error.illegal.modifiers"
 799                         : "jdk.eval.warn.illegal.modifiers";
 800             }
 801 
 802             @Override
 803             public String getMessage(Locale locale) {
 804                 return message;
 805             }
 806         }
 807 
 808         List<Modifier> list = new ArrayList<>();
 809         boolean fatal = false;
 810         for (Modifier mod : modtree.getFlags()) {
 811             switch (mod) {
 812                 case SYNCHRONIZED:
 813                 case NATIVE:
 814                     list.add(mod);
 815                     fatal = true;
 816                     break;
 817                 case ABSTRACT:
 818                     if (isAbstractProhibited) {
 819                         list.add(mod);
 820                         fatal = true;
 821                     }
 822                     break;
 823                 case PUBLIC:
 824                 case PROTECTED:
 825                 case PRIVATE:
 826                 case STATIC:
 827                 case FINAL:
 828                     list.add(mod);
 829                     break;
 830             }
 831         }
 832         return list.isEmpty()
 833                 ? new DiagList()
 834                 : new DiagList(new ModifierDiagnostic(list, fatal));
 835     }
 836 
 837 }