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 }