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 }