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