1/* 2 * Copyright (c) 2015, 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 26package jdk.jshell; 27 28import java.util.ArrayList; 29import java.util.Collection; 30import java.util.Collections; 31import java.util.LinkedHashSet; 32import java.util.List; 33import java.util.Set; 34import java.util.stream.Stream; 35import jdk.jshell.ClassTracker.ClassInfo; 36import jdk.jshell.Snippet.Kind; 37import jdk.jshell.Snippet.Status; 38import jdk.jshell.Snippet.SubKind; 39import jdk.jshell.TaskFactory.AnalyzeTask; 40import jdk.jshell.TaskFactory.CompileTask; 41import jdk.jshell.spi.ExecutionControl.ClassBytecodes; 42import jdk.jshell.spi.ExecutionControl.ClassInstallException; 43import jdk.jshell.spi.ExecutionControl.EngineTerminationException; 44import jdk.jshell.spi.ExecutionControl.NotImplementedException; 45import static java.util.stream.Collectors.toList; 46import static java.util.stream.Collectors.toSet; 47import static jdk.internal.jshell.debug.InternalDebugControl.DBG_EVNT; 48import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN; 49import static jdk.internal.jshell.debug.InternalDebugControl.DBG_WRAP; 50import static jdk.jshell.Snippet.Status.OVERWRITTEN; 51import static jdk.jshell.Snippet.Status.RECOVERABLE_DEFINED; 52import static jdk.jshell.Snippet.Status.RECOVERABLE_NOT_DEFINED; 53import static jdk.jshell.Snippet.Status.REJECTED; 54import static jdk.jshell.Snippet.Status.VALID; 55import static jdk.jshell.Util.PARSED_LOCALE; 56import static jdk.jshell.Util.expunge; 57 58/** 59 * Tracks the compilation and load of a new or updated snippet. 60 * @author Robert Field 61 */ 62final class Unit { 63 64 private final JShell state; 65 private final Snippet si; 66 private final Snippet siOld; 67 private final boolean isDependency; 68 private final boolean isNew; 69 private final Snippet causalSnippet; 70 private final DiagList generatedDiagnostics; 71 72 private int seq; 73 private String classNameInitial; 74 private Wrap activeGuts; 75 private Status status; 76 private Status prevStatus; 77 private boolean signatureChanged; 78 private DiagList compilationDiagnostics; 79 private DiagList recompilationDiagnostics = null; 80 private List<String> unresolved; 81 private SnippetEvent replaceOldEvent; 82 private List<SnippetEvent> secondaryEvents; 83 private boolean isAttemptingCorral; 84 private List<ClassInfo> toRedefine; 85 private boolean dependenciesNeeded; 86 87 Unit(JShell state, Snippet si, Snippet causalSnippet, 88 DiagList generatedDiagnostics) { 89 this.state = state; 90 this.si = si; 91 this.isDependency = causalSnippet != null; 92 this.siOld = isDependency 93 ? si 94 : state.maps.getSnippet(si.key()); 95 this.isNew = siOld == null; 96 this.causalSnippet = causalSnippet; 97 this.generatedDiagnostics = generatedDiagnostics; 98 99 this.seq = isNew? 0 : siOld.sequenceNumber(); 100 this.classNameInitial = isNew? "<none>" : siOld.className(); 101 this.prevStatus = (isNew || isDependency) 102 ? si.status() 103 : siOld.status(); 104 si.setSequenceNumber(seq); 105 } 106 107 // Drop entry 108 Unit(JShell state, Snippet si) { 109 this.state = state; 110 this.si = si; 111 this.siOld = null; 112 this.isDependency = false; 113 this.isNew = false; 114 this.causalSnippet = null; 115 this.generatedDiagnostics = new DiagList(); 116 this.prevStatus = si.status(); 117 si.setDropped(); 118 this.status = si.status(); 119 } 120 121 @Override 122 public int hashCode() { 123 return si.hashCode(); 124 } 125 126 @Override 127 public boolean equals(Object o) { 128 return (o instanceof Unit) 129 ? si.equals(((Unit) o).si) 130 : false; 131 } 132 133 Snippet snippet() { 134 return si; 135 } 136 137 boolean isDependency() { 138 return isDependency; 139 } 140 141 void initialize() { 142 isAttemptingCorral = false; 143 dependenciesNeeded = false; 144 toRedefine = null; // assure NPE if classToLoad not called 145 activeGuts = si.guts(); 146 markOldDeclarationOverwritten(); 147 } 148 149 // Set the outer wrap of our Snippet 150 void setWrap(Collection<Unit> exceptUnit, Collection<Unit> plusUnfiltered) { 151 if (isImport()) { 152 si.setOuterWrap(state.outerMap.wrapImport(activeGuts, si)); 153 } else { 154 // Collect Units for be wrapped together. Just this except for overloaded methods 155 List<Unit> units; 156 if (snippet().kind() == Kind.METHOD) { 157 String name = ((MethodSnippet) snippet()).name(); 158 units = plusUnfiltered.stream() 159 .filter(u -> u.snippet().kind() == Kind.METHOD && 160 ((MethodSnippet) u.snippet()).name().equals(name)) 161 .collect(toList()); 162 } else { 163 units = Collections.singletonList(this); 164 } 165 // Keys to exclude from imports 166 Set<Key> except = exceptUnit.stream() 167 .map(u -> u.snippet().key()) 168 .collect(toSet()); 169 // Snippets to add to imports 170 Collection<Snippet> plus = plusUnfiltered.stream() 171 .filter(u -> !units.contains(u)) 172 .map(Unit::snippet) 173 .collect(toList()); 174 // Snippets to wrap in an outer 175 List<Snippet> snippets = units.stream() 176 .map(Unit::snippet) 177 .collect(toList()); 178 // Snippet wraps to wrap in an outer 179 List<Wrap> wraps = units.stream() 180 .map(u -> u.activeGuts) 181 .collect(toList()); 182 // Set the outer wrap for this snippet 183 si.setOuterWrap(state.outerMap.wrapInClass(except, plus, snippets, wraps)); 184 state.debug(DBG_WRAP, "++setWrap() %s\n%s\n", 185 si, si.outerWrap().wrapped()); 186 } 187 } 188 189 void setDiagnostics(AnalyzeTask ct) { 190 setDiagnostics(ct.getDiagnostics().ofUnit(this)); 191 } 192 193 void setDiagnostics(DiagList diags) { 194 compilationDiagnostics = diags; 195 UnresolvedExtractor ue = new UnresolvedExtractor(diags); 196 unresolved = ue.unresolved(); 197 state.debug(DBG_GEN, "++setCompilationInfo() %s\n%s\n-- diags: %s\n", 198 si, si.outerWrap().wrapped(), diags); 199 } 200 201 private boolean isRecoverable() { 202 // Unit failed, use corralling if it is defined on this Snippet, 203 // and either all the errors are resolution errors or this is a 204 // redeclare of an existing method 205 return compilationDiagnostics.hasErrors() 206 && si instanceof DeclarationSnippet 207 && (isDependency() 208 || (si.subKind() != SubKind.VAR_DECLARATION_WITH_INITIALIZER_SUBKIND 209 && compilationDiagnostics.hasResolutionErrorsAndNoOthers())); 210 } 211 212 /** 213 * If it meets the conditions for corralling, install the corralled wrap 214 * @return true is the corralled wrap was installed 215 */ 216 boolean corralIfNeeded(Collection<Unit> working) { 217 if (isRecoverable() 218 && si.corralled() != null) { 219 activeGuts = si.corralled(); 220 setWrap(working, working); 221 return isAttemptingCorral = true; 222 } 223 return isAttemptingCorral = false; 224 } 225 226 void setCorralledDiagnostics(AnalyzeTask cct) { 227 // set corralled diagnostics, but don't reset unresolved 228 recompilationDiagnostics = cct.getDiagnostics().ofUnit(this); 229 state.debug(DBG_GEN, "++recomp %s\n%s\n-- diags: %s\n", 230 si, si.outerWrap().wrapped(), recompilationDiagnostics); 231 } 232 233 boolean smashingErrorDiagnostics(CompileTask ct) { 234 if (isDefined()) { 235 // set corralled diagnostics, but don't reset unresolved 236 DiagList dl = ct.getDiagnostics().ofUnit(this); 237 if (dl.hasErrors()) { 238 setDiagnostics(dl); 239 status = RECOVERABLE_NOT_DEFINED; 240 // overwrite orginal bytes 241 state.debug(DBG_GEN, "++smashingErrorDiagnostics %s\n%s\n-- diags: %s\n", 242 si, si.outerWrap().wrapped(), dl); 243 return true; 244 } 245 } 246 return false; 247 } 248 249 void setStatus(AnalyzeTask at) { 250 if (!compilationDiagnostics.hasErrors()) { 251 status = VALID; 252 } else if (isRecoverable()) { 253 if (isAttemptingCorral && !recompilationDiagnostics.hasErrors()) { 254 status = RECOVERABLE_DEFINED; 255 } else { 256 status = RECOVERABLE_NOT_DEFINED; 257 } 258 } else { 259 status = REJECTED; 260 } 261 checkForOverwrite(at); 262 263 state.debug(DBG_GEN, "setStatus() %s - status: %s\n", 264 si, status); 265 } 266 267 boolean isDefined() { 268 return status.isDefined(); 269 } 270 271 /** 272 * Process the class information from the last compile. Requires loading of 273 * returned list. 274 * 275 * @return the list of classes to load 276 */ 277 Stream<ClassBytecodes> classesToLoad(List<String> classnames) { 278 toRedefine = new ArrayList<>(); 279 List<ClassBytecodes> toLoad = new ArrayList<>(); 280 if (status.isDefined() && !isImport()) { 281 // Classes should only be loaded/redefined if the compile left them 282 // in a defined state. Imports do not have code and are not loaded. 283 for (String cn : classnames) { 284 ClassInfo ci = state.classTracker.get(cn); 285 if (ci.isLoaded()) { 286 if (ci.isCurrent()) { 287 // nothing to do 288 } else { 289 toRedefine.add(ci); 290 } 291 } else { 292 // If not loaded, add to the list of classes to load. 293 toLoad.add(ci.toClassBytecodes()); 294 dependenciesNeeded = true; 295 } 296 } 297 } 298 return toLoad.stream(); 299 } 300 301 /** 302 * Redefine classes needing redefine. classesToLoad() must be called first. 303 * 304 * @return true if all redefines succeeded (can be vacuously true) 305 */ 306 boolean doRedefines() { 307 if (toRedefine.isEmpty()) { 308 return true; 309 } 310 ClassBytecodes[] cbcs = toRedefine.stream() 311 .map(ClassInfo::toClassBytecodes) 312 .toArray(ClassBytecodes[]::new); 313 try { 314 state.executionControl().redefine(cbcs); 315 state.classTracker.markLoaded(cbcs); 316 return true; 317 } catch (ClassInstallException ex) { 318 state.classTracker.markLoaded(cbcs, ex.installed()); 319 return false; 320 } catch (EngineTerminationException ex) { 321 state.closeDown(); 322 return false; 323 } catch (NotImplementedException ex) { 324 return false; 325 } 326 } 327 328 void markForReplacement() { 329 // increment for replace class wrapper 330 si.setSequenceNumber(++seq); 331 } 332 333 private boolean isImport() { 334 return si.kind() == Kind.IMPORT; 335 } 336 337 private boolean sigChanged() { 338 return (status.isDefined() != prevStatus.isDefined()) 339 || (status.isDefined() && !si.className().equals(classNameInitial)) 340 || signatureChanged; 341 } 342 343 Stream<Unit> effectedDependents() { 344 //System.err.printf("effectedDependents sigChanged=%b dependenciesNeeded=%b status=%s\n", 345 // sigChanged(), dependenciesNeeded, status); 346 return sigChanged() || dependenciesNeeded || status == RECOVERABLE_NOT_DEFINED 347 ? dependents() 348 : Stream.empty(); 349 } 350 351 Stream<Unit> dependents() { 352 return state.maps.getDependents(si) 353 .stream() 354 .filter(xsi -> xsi != si && xsi.status().isActive()) 355 .map(xsi -> new Unit(state, xsi, si, new DiagList())); 356 } 357 358 void finish() { 359 recordCompilation(); 360 state.maps.installSnippet(si); 361 } 362 363 private void markOldDeclarationOverwritten() { 364 if (si != siOld && siOld != null && siOld.status().isActive()) { 365 // Mark the old declaraion as replaced 366 replaceOldEvent = new SnippetEvent(siOld, 367 siOld.status(), OVERWRITTEN, 368 false, si, null, null); 369 siOld.setOverwritten(); 370 } 371 } 372 373 private DiagList computeDiagnostics() { 374 DiagList diagnostics = new DiagList(); 375 DiagList diags = compilationDiagnostics; 376 if (status == RECOVERABLE_DEFINED || status == RECOVERABLE_NOT_DEFINED) { 377 UnresolvedExtractor ue = new UnresolvedExtractor(diags); 378 diagnostics.addAll(ue.otherAll()); 379 } else { 380 unresolved = Collections.emptyList(); 381 diagnostics.addAll(diags); 382 } 383 diagnostics.addAll(generatedDiagnostics); 384 return diagnostics; 385 } 386 387 private void recordCompilation() { 388 state.maps.mapDependencies(si); 389 DiagList diags = computeDiagnostics(); 390 si.setCompilationStatus(status, unresolved, diags); 391 state.debug(DBG_GEN, "recordCompilation: %s -- status %s, unresolved %s\n", 392 si, status, unresolved); 393 } 394 395 private void checkForOverwrite(AnalyzeTask at) { 396 secondaryEvents = new ArrayList<>(); 397 if (replaceOldEvent != null) secondaryEvents.add(replaceOldEvent); 398 399 // Defined methods can overwrite methods of other (equivalent) snippets 400 if (isNew && si.kind() == Kind.METHOD && status.isDefined()) { 401 MethodSnippet msi = (MethodSnippet)si; 402 String oqpt = msi.qualifiedParameterTypes(); 403 String nqpt = computeQualifiedParameterTypes(at, msi); 404 if (!nqpt.equals(oqpt)) { 405 msi.setQualifiedParamaterTypes(nqpt); 406 Status overwrittenStatus = overwriteMatchingMethod(msi); 407 if (overwrittenStatus != null) { 408 prevStatus = overwrittenStatus; 409 signatureChanged = true; 410 } 411 } 412 } 413 } 414 415 // Check if there is a method whose user-declared parameter types are 416 // different (and thus has a different snippet) but whose compiled parameter 417 // types are the same. if so, consider it an overwrite replacement. 418 private Status overwriteMatchingMethod(MethodSnippet msi) { 419 String qpt = msi.qualifiedParameterTypes(); 420 List<MethodSnippet> matching = state.methods() 421 .filter(sn -> 422 sn != null 423 && sn != msi 424 && sn.status().isActive() 425 && sn.name().equals(msi.name()) 426 && qpt.equals(sn.qualifiedParameterTypes())) 427 .collect(toList()); 428 429 // Look through all methods for a method of the same name, with the 430 // same computed qualified parameter types 431 Status overwrittenStatus = null; 432 for (MethodSnippet sn : matching) { 433 overwrittenStatus = sn.status(); 434 SnippetEvent se = new SnippetEvent( 435 sn, overwrittenStatus, OVERWRITTEN, 436 false, msi, null, null); 437 sn.setOverwritten(); 438 secondaryEvents.add(se); 439 state.debug(DBG_EVNT, 440 "Overwrite event #%d -- key: %s before: %s status: %s sig: %b cause: %s\n", 441 secondaryEvents.size(), se.snippet(), se.previousStatus(), 442 se.status(), se.isSignatureChange(), se.causeSnippet()); 443 } 444 return overwrittenStatus; 445 } 446 447 private String computeQualifiedParameterTypes(AnalyzeTask at, MethodSnippet msi) { 448 String rawSig = TreeDissector.createBySnippet(at, msi).typeOfMethod(msi); 449 String signature = expunge(rawSig); 450 int paren = signature.lastIndexOf(')'); 451 452 // Extract the parameter type string from the method signature, 453 // if method did not compile use the user-supplied parameter types 454 return paren >= 0 455 ? signature.substring(0, paren + 1) 456 : msi.parameterTypes(); 457 } 458 459 SnippetEvent event(String value, JShellException exception) { 460 boolean wasSignatureChanged = sigChanged(); 461 state.debug(DBG_EVNT, "Snippet: %s id: %s before: %s status: %s sig: %b cause: %s\n", 462 si, si.id(), prevStatus, si.status(), wasSignatureChanged, causalSnippet); 463 return new SnippetEvent(si, prevStatus, si.status(), 464 wasSignatureChanged, causalSnippet, value, exception); 465 } 466 467 List<SnippetEvent> secondaryEvents() { 468 return secondaryEvents==null 469 ? Collections.emptyList() 470 : secondaryEvents; 471 } 472 473 @Override 474 public String toString() { 475 return "Unit(" + si.name() + ")"; 476 } 477 478 /** 479 * Separate out the unresolvedDependencies errors from both the other 480 * corralling errors and the overall errors. 481 */ 482 private static class UnresolvedExtractor { 483 484 private static final String RESOLVE_ERROR_SYMBOL = "symbol:"; 485 private static final String RESOLVE_ERROR_LOCATION = "location:"; 486 487 //TODO extract from tree instead -- note: internationalization 488 private final Set<String> unresolved = new LinkedHashSet<>(); 489 private final DiagList otherErrors = new DiagList(); 490 private final DiagList otherAll = new DiagList(); 491 492 UnresolvedExtractor(DiagList diags) { 493 for (Diag diag : diags) { 494 if (diag.isError()) { 495 if (diag.isResolutionError()) { 496 String m = diag.getMessage(PARSED_LOCALE); 497 int symPos = m.indexOf(RESOLVE_ERROR_SYMBOL); 498 if (symPos >= 0) { 499 m = m.substring(symPos + RESOLVE_ERROR_SYMBOL.length()); 500 int symLoc = m.indexOf(RESOLVE_ERROR_LOCATION); 501 if (symLoc >= 0) { 502 m = m.substring(0, symLoc); 503 } 504 m = m.trim(); 505 unresolved.add(m); 506 continue; 507 } 508 } 509 otherErrors.add(diag); 510 } 511 otherAll.add(diag); 512 } 513 } 514 515 DiagList otherCorralledErrors() { 516 return otherErrors; 517 } 518 519 DiagList otherAll() { 520 return otherAll; 521 } 522 523 List<String> unresolved() { 524 return new ArrayList<>(unresolved); 525 } 526 } 527} 528