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