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