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