Eval.java revision 3695:40468274ff3b
1/*
2 * Copyright (c) 2014, 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 */
25package jdk.jshell;
26
27import java.util.ArrayList;
28import java.util.Collection;
29import java.util.Collections;
30import java.util.List;
31import java.util.Locale;
32import java.util.regex.Matcher;
33import java.util.regex.Pattern;
34import java.util.stream.Collectors;
35import javax.lang.model.element.Modifier;
36import com.sun.source.tree.ArrayTypeTree;
37import com.sun.source.tree.AssignmentTree;
38import com.sun.source.tree.ClassTree;
39import com.sun.source.tree.ExpressionTree;
40import com.sun.source.tree.IdentifierTree;
41import com.sun.source.tree.MethodTree;
42import com.sun.source.tree.ModifiersTree;
43import com.sun.source.tree.Tree;
44import com.sun.source.tree.VariableTree;
45import com.sun.tools.javac.tree.JCTree;
46import com.sun.tools.javac.tree.Pretty;
47import java.io.IOException;
48import java.io.StringWriter;
49import java.io.Writer;
50import java.util.LinkedHashSet;
51import java.util.Set;
52import jdk.jshell.Key.ErroneousKey;
53import jdk.jshell.Key.MethodKey;
54import jdk.jshell.Key.TypeDeclKey;
55import jdk.jshell.Snippet.Kind;
56import jdk.jshell.Snippet.SubKind;
57import jdk.jshell.TaskFactory.AnalyzeTask;
58import jdk.jshell.TaskFactory.BaseTask;
59import jdk.jshell.TaskFactory.CompileTask;
60import jdk.jshell.TaskFactory.ParseTask;
61import jdk.jshell.TreeDissector.ExpressionInfo;
62import jdk.jshell.Wrap.Range;
63import jdk.jshell.Snippet.Status;
64import jdk.jshell.spi.ExecutionControl.ClassBytecodes;
65import jdk.jshell.spi.ExecutionControl.ClassInstallException;
66import jdk.jshell.spi.ExecutionControl.EngineTerminationException;
67import jdk.jshell.spi.ExecutionControl.InternalException;
68import jdk.jshell.spi.ExecutionControl.NotImplementedException;
69import jdk.jshell.spi.ExecutionControl.ResolutionException;
70import jdk.jshell.spi.ExecutionControl.RunException;
71import jdk.jshell.spi.ExecutionControl.UserException;
72import static java.util.stream.Collectors.toList;
73import static java.util.stream.Collectors.toSet;
74import static java.util.Collections.singletonList;
75import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
76import static jdk.jshell.Util.DOIT_METHOD_NAME;
77import static jdk.jshell.Util.PREFIX_PATTERN;
78import static jdk.jshell.Util.expunge;
79import static jdk.jshell.Snippet.SubKind.SINGLE_TYPE_IMPORT_SUBKIND;
80import static jdk.jshell.Snippet.SubKind.SINGLE_STATIC_IMPORT_SUBKIND;
81import static jdk.jshell.Snippet.SubKind.TYPE_IMPORT_ON_DEMAND_SUBKIND;
82import static jdk.jshell.Snippet.SubKind.STATIC_IMPORT_ON_DEMAND_SUBKIND;
83
84/**
85 * The Evaluation Engine. Source internal analysis, wrapping control,
86 * compilation, declaration. redefinition, replacement, and execution.
87 *
88 * @author Robert Field
89 */
90class Eval {
91
92    private static final Pattern IMPORT_PATTERN = Pattern.compile("import\\p{javaWhitespace}+(?<static>static\\p{javaWhitespace}+)?(?<fullname>[\\p{L}\\p{N}_\\$\\.]+\\.(?<name>[\\p{L}\\p{N}_\\$]+|\\*))");
93
94    private int varNumber = 0;
95
96    private final JShell state;
97
98    Eval(JShell state) {
99        this.state = state;
100    }
101
102    /**
103     * Evaluates a snippet of source.
104     *
105     * @param userSource the source of the snippet
106     * @return the list of primary and update events
107     * @throws IllegalStateException
108     */
109    List<SnippetEvent> eval(String userSource) throws IllegalStateException {
110        List<SnippetEvent> allEvents = new ArrayList<>();
111        for (Snippet snip : sourceToSnippets(userSource)) {
112            if (snip.kind() == Kind.ERRONEOUS) {
113                state.maps.installSnippet(snip);
114                allEvents.add(new SnippetEvent(
115                        snip, Status.NONEXISTENT, Status.REJECTED,
116                        false, null, null, null));
117            } else {
118                allEvents.addAll(declare(snip, snip.syntheticDiags()));
119            }
120        }
121        return allEvents;
122    }
123
124    /**
125     * Converts the user source of a snippet into a Snippet list -- Snippet will
126     * have wrappers.
127     *
128     * @param userSource the source of the snippet
129     * @return usually a singleton list of Snippet, but may be empty or multiple
130     */
131    List<Snippet> sourceToSnippetsWithWrappers(String userSource) {
132        List<Snippet> snippets = sourceToSnippets(userSource);
133        for (Snippet snip : snippets) {
134            if (snip.outerWrap() == null) {
135                snip.setOuterWrap(
136                        (snip.kind() == Kind.IMPORT)
137                                ? state.outerMap.wrapImport(snip.guts(), snip)
138                                : state.outerMap.wrapInTrialClass(snip.guts())
139                );
140            }
141        }
142        return snippets;
143    }
144
145    /**
146     * Converts the user source of a snippet into a Snippet object (or list of
147     * objects in the case of: int x, y, z;).  Does not install the Snippets
148     * or execute them.
149     *
150     * @param userSource the source of the snippet
151     * @return usually a singleton list of Snippet, but may be empty or multiple
152     */
153    private List<Snippet> sourceToSnippets(String userSource) {
154        String compileSource = Util.trimEnd(new MaskCommentsAndModifiers(userSource, false).cleared());
155        if (compileSource.length() == 0) {
156            return Collections.emptyList();
157        }
158        ParseTask pt = state.taskFactory.new ParseTask(compileSource);
159        List<? extends Tree> units = pt.units();
160        if (units.isEmpty()) {
161            return compileFailResult(pt, userSource, Kind.ERRONEOUS);
162        }
163        Tree unitTree = units.get(0);
164        if (pt.getDiagnostics().hasOtherThanNotStatementErrors()) {
165            return compileFailResult(pt, userSource, kindOfTree(unitTree));
166        }
167
168        // Erase illegal/ignored modifiers
169        compileSource = new MaskCommentsAndModifiers(compileSource, true).cleared();
170
171        state.debug(DBG_GEN, "Kind: %s -- %s\n", unitTree.getKind(), unitTree);
172        switch (unitTree.getKind()) {
173            case IMPORT:
174                return processImport(userSource, compileSource);
175            case VARIABLE:
176                return processVariables(userSource, units, compileSource, pt);
177            case EXPRESSION_STATEMENT:
178                return processExpression(userSource, compileSource);
179            case CLASS:
180                return processClass(userSource, unitTree, compileSource, SubKind.CLASS_SUBKIND, pt);
181            case ENUM:
182                return processClass(userSource, unitTree, compileSource, SubKind.ENUM_SUBKIND, pt);
183            case ANNOTATION_TYPE:
184                return processClass(userSource, unitTree, compileSource, SubKind.ANNOTATION_TYPE_SUBKIND, pt);
185            case INTERFACE:
186                return processClass(userSource, unitTree, compileSource, SubKind.INTERFACE_SUBKIND, pt);
187            case METHOD:
188                return processMethod(userSource, unitTree, compileSource, pt);
189            default:
190                return processStatement(userSource, compileSource);
191        }
192    }
193
194    private List<Snippet> processImport(String userSource, String compileSource) {
195        Wrap guts = Wrap.simpleWrap(compileSource);
196        Matcher mat = IMPORT_PATTERN.matcher(compileSource);
197        String fullname;
198        String name;
199        boolean isStatic;
200        if (mat.find()) {
201            isStatic = mat.group("static") != null;
202            name = mat.group("name");
203            fullname = mat.group("fullname");
204        } else {
205            // bad import -- fake it
206            isStatic = compileSource.contains("static");
207            name = fullname = compileSource;
208        }
209        String fullkey = (isStatic ? "static-" : "") + fullname;
210        boolean isStar = name.equals("*");
211        String keyName = isStar
212                ? fullname
213                : name;
214        SubKind snippetKind = isStar
215                ? (isStatic ? STATIC_IMPORT_ON_DEMAND_SUBKIND : TYPE_IMPORT_ON_DEMAND_SUBKIND)
216                : (isStatic ? SINGLE_STATIC_IMPORT_SUBKIND : SINGLE_TYPE_IMPORT_SUBKIND);
217        Snippet snip = new ImportSnippet(state.keyMap.keyForImport(keyName, snippetKind),
218                userSource, guts, fullname, name, snippetKind, fullkey, isStatic, isStar);
219        return singletonList(snip);
220    }
221
222    private static class EvalPretty extends Pretty {
223
224        private final Writer out;
225
226        public EvalPretty(Writer writer, boolean bln) {
227            super(writer, bln);
228            this.out = writer;
229        }
230
231        /**
232         * Print string, DO NOT replacing all non-ascii character with unicode
233         * escapes.
234         */
235        @Override
236        public void print(Object o) throws IOException {
237            out.write(o.toString());
238        }
239
240        static String prettyExpr(JCTree tree, boolean bln) {
241            StringWriter out = new StringWriter();
242            try {
243                new EvalPretty(out, bln).printExpr(tree);
244            } catch (IOException e) {
245                throw new AssertionError(e);
246            }
247            return out.toString();
248        }
249    }
250
251    private List<Snippet> processVariables(String userSource, List<? extends Tree> units, String compileSource, ParseTask pt) {
252        List<Snippet> snippets = new ArrayList<>();
253        TreeDissector dis = TreeDissector.createByFirstClass(pt);
254        for (Tree unitTree : units) {
255            VariableTree vt = (VariableTree) unitTree;
256            String name = vt.getName().toString();
257            String typeName = EvalPretty.prettyExpr((JCTree) vt.getType(), false);
258            Tree baseType = vt.getType();
259            TreeDependencyScanner tds = new TreeDependencyScanner();
260            tds.scan(baseType); // Not dependent on initializer
261            StringBuilder sbBrackets = new StringBuilder();
262            while (baseType instanceof ArrayTypeTree) {
263                //TODO handle annotations too
264                baseType = ((ArrayTypeTree) baseType).getType();
265                sbBrackets.append("[]");
266            }
267            Range rtype = dis.treeToRange(baseType);
268            Range runit = dis.treeToRange(vt);
269            runit = new Range(runit.begin, runit.end - 1);
270            ExpressionTree it = vt.getInitializer();
271            Range rinit = null;
272            int nameMax = runit.end - 1;
273            SubKind subkind;
274            if (it != null) {
275                subkind = SubKind.VAR_DECLARATION_WITH_INITIALIZER_SUBKIND;
276                rinit = dis.treeToRange(it);
277                nameMax = rinit.begin - 1;
278            } else {
279                subkind = SubKind.VAR_DECLARATION_SUBKIND;
280            }
281            int nameStart = compileSource.lastIndexOf(name, nameMax);
282            if (nameStart < 0) {
283                throw new AssertionError("Name '" + name + "' not found");
284            }
285            int nameEnd = nameStart + name.length();
286            Range rname = new Range(nameStart, nameEnd);
287            Wrap guts = Wrap.varWrap(compileSource, rtype, sbBrackets.toString(), rname, rinit);
288            DiagList modDiag = modifierDiagnostics(vt.getModifiers(), dis, true);
289            Snippet snip = new VarSnippet(state.keyMap.keyForVariable(name), userSource, guts,
290                    name, subkind, typeName,
291                    tds.declareReferences(), modDiag);
292            snippets.add(snip);
293        }
294        return snippets;
295    }
296
297    private List<Snippet> processExpression(String userSource, String compileSource) {
298        String name = null;
299        ExpressionInfo ei = typeOfExpression(compileSource);
300        ExpressionTree assignVar;
301        Wrap guts;
302        Snippet snip;
303        if (ei != null && ei.isNonVoid) {
304            String typeName = ei.typeName;
305            SubKind subkind;
306            if (ei.tree instanceof IdentifierTree) {
307                IdentifierTree id = (IdentifierTree) ei.tree;
308                name = id.getName().toString();
309                subkind = SubKind.VAR_VALUE_SUBKIND;
310
311            } else if (ei.tree instanceof AssignmentTree
312                    && (assignVar = ((AssignmentTree) ei.tree).getVariable()) instanceof IdentifierTree) {
313                name = assignVar.toString();
314                subkind = SubKind.ASSIGNMENT_SUBKIND;
315            } else {
316                subkind = SubKind.OTHER_EXPRESSION_SUBKIND;
317            }
318            if (shouldGenTempVar(subkind)) {
319                if (state.tempVariableNameGenerator != null) {
320                    name = state.tempVariableNameGenerator.get();
321                }
322                while (name == null || state.keyMap.doesVariableNameExist(name)) {
323                    name = "$" + ++varNumber;
324                }
325                guts = Wrap.tempVarWrap(compileSource, typeName, name);
326                Collection<String> declareReferences = null; //TODO
327                snip = new VarSnippet(state.keyMap.keyForVariable(name), userSource, guts,
328                        name, SubKind.TEMP_VAR_EXPRESSION_SUBKIND, typeName, declareReferences, null);
329            } else {
330                guts = Wrap.methodReturnWrap(compileSource);
331                snip = new ExpressionSnippet(state.keyMap.keyForExpression(name, typeName), userSource, guts,
332                        name, subkind);
333            }
334        } else {
335            guts = Wrap.methodWrap(compileSource);
336            if (ei == null) {
337                // We got no type info, check for not a statement by trying
338                AnalyzeTask at = trialCompile(guts);
339                if (at.getDiagnostics().hasNotStatement()) {
340                    guts = Wrap.methodReturnWrap(compileSource);
341                    at = trialCompile(guts);
342                }
343                if (at.hasErrors()) {
344                    return compileFailResult(at, userSource, Kind.EXPRESSION);
345                }
346            }
347            snip = new StatementSnippet(state.keyMap.keyForStatement(), userSource, guts);
348        }
349        return singletonList(snip);
350    }
351
352    private List<Snippet> processClass(String userSource, Tree unitTree, String compileSource, SubKind snippetKind, ParseTask pt) {
353        TreeDependencyScanner tds = new TreeDependencyScanner();
354        tds.scan(unitTree);
355
356        TreeDissector dis = TreeDissector.createByFirstClass(pt);
357
358        ClassTree klassTree = (ClassTree) unitTree;
359        String name = klassTree.getSimpleName().toString();
360        DiagList modDiag = modifierDiagnostics(klassTree.getModifiers(), dis, false);
361        TypeDeclKey key = state.keyMap.keyForClass(name);
362        // Corralling mutates.  Must be last use of pt, unitTree, klassTree
363        Wrap corralled = new Corraller(key.index(), pt.getContext()).corralType(klassTree);
364
365        Wrap guts = Wrap.classMemberWrap(compileSource);
366        Snippet snip = new TypeDeclSnippet(key, userSource, guts,
367                name, snippetKind,
368                corralled, tds.declareReferences(), tds.bodyReferences(), modDiag);
369        return singletonList(snip);
370    }
371
372    private List<Snippet> processStatement(String userSource, String compileSource) {
373        Wrap guts = Wrap.methodWrap(compileSource);
374        // Check for unreachable by trying
375        AnalyzeTask at = trialCompile(guts);
376        if (at.hasErrors()) {
377            if (at.getDiagnostics().hasUnreachableError()) {
378                guts = Wrap.methodUnreachableSemiWrap(compileSource);
379                at = trialCompile(guts);
380                if (at.hasErrors()) {
381                    if (at.getDiagnostics().hasUnreachableError()) {
382                        // Without ending semicolon
383                        guts = Wrap.methodUnreachableWrap(compileSource);
384                        at = trialCompile(guts);
385                    }
386                    if (at.hasErrors()) {
387                        return compileFailResult(at, userSource, Kind.STATEMENT);
388                    }
389                }
390            } else {
391                return compileFailResult(at, userSource, Kind.STATEMENT);
392            }
393        }
394        Snippet snip = new StatementSnippet(state.keyMap.keyForStatement(), userSource, guts);
395        return singletonList(snip);
396    }
397
398    private AnalyzeTask trialCompile(Wrap guts) {
399        OuterWrap outer = state.outerMap.wrapInTrialClass(guts);
400        return state.taskFactory.new AnalyzeTask(outer);
401    }
402
403    private List<Snippet> processMethod(String userSource, Tree unitTree, String compileSource, ParseTask pt) {
404        TreeDependencyScanner tds = new TreeDependencyScanner();
405        tds.scan(unitTree);
406        TreeDissector dis = TreeDissector.createByFirstClass(pt);
407
408        MethodTree mt = (MethodTree) unitTree;
409        String name = mt.getName().toString();
410        String parameterTypes
411                = mt.getParameters()
412                .stream()
413                .map(param -> dis.treeToRange(param.getType()).part(compileSource))
414                .collect(Collectors.joining(","));
415        Tree returnType = mt.getReturnType();
416        DiagList modDiag = modifierDiagnostics(mt.getModifiers(), dis, true);
417        MethodKey key = state.keyMap.keyForMethod(name, parameterTypes);
418        // Corralling mutates.  Must be last use of pt, unitTree, mt
419        Wrap corralled = new Corraller(key.index(), pt.getContext()).corralMethod(mt);
420
421        if (modDiag.hasErrors()) {
422            return compileFailResult(modDiag, userSource, Kind.METHOD);
423        }
424        Wrap guts = Wrap.classMemberWrap(compileSource);
425        Range typeRange = dis.treeToRange(returnType);
426        String signature = "(" + parameterTypes + ")" + typeRange.part(compileSource);
427
428        Snippet snip = new MethodSnippet(key, userSource, guts,
429                name, signature,
430                corralled, tds.declareReferences(), tds.bodyReferences(), modDiag);
431        return singletonList(snip);
432    }
433
434    private Kind kindOfTree(Tree tree) {
435        switch (tree.getKind()) {
436            case IMPORT:
437                return Kind.IMPORT;
438            case VARIABLE:
439                return Kind.VAR;
440            case EXPRESSION_STATEMENT:
441                return Kind.EXPRESSION;
442            case CLASS:
443            case ENUM:
444            case ANNOTATION_TYPE:
445            case INTERFACE:
446                return Kind.TYPE_DECL;
447            case METHOD:
448                return Kind.METHOD;
449            default:
450                return Kind.STATEMENT;
451        }
452    }
453
454    /**
455     * The snippet has failed, return with the rejected snippet
456     *
457     * @param xt the task from which to extract the failure diagnostics
458     * @param userSource the incoming bad user source
459     * @return a rejected snippet
460     */
461    private List<Snippet> compileFailResult(BaseTask xt, String userSource, Kind probableKind) {
462        return compileFailResult(xt.getDiagnostics(), userSource, probableKind);
463    }
464
465    /**
466     * The snippet has failed, return with the rejected snippet
467     *
468     * @param diags the failure diagnostics
469     * @param userSource the incoming bad user source
470     * @return a rejected snippet
471     */
472    private List<Snippet> compileFailResult(DiagList diags, String userSource, Kind probableKind) {
473        ErroneousKey key = state.keyMap.keyForErroneous();
474        Snippet snip = new ErroneousSnippet(key, userSource, null,
475                probableKind, SubKind.UNKNOWN_SUBKIND);
476        snip.setFailed(diags);
477
478        // Install  wrapper for query by SourceCodeAnalysis.wrapper
479        String compileSource = Util.trimEnd(new MaskCommentsAndModifiers(userSource, true).cleared());
480        OuterWrap outer;
481        switch (probableKind) {
482            case IMPORT:
483                outer = state.outerMap.wrapImport(Wrap.simpleWrap(compileSource), snip);
484                break;
485            case EXPRESSION:
486                outer = state.outerMap.wrapInTrialClass(Wrap.methodReturnWrap(compileSource));
487                break;
488            case VAR:
489            case TYPE_DECL:
490            case METHOD:
491                outer = state.outerMap.wrapInTrialClass(Wrap.classMemberWrap(compileSource));
492                break;
493            default:
494                outer = state.outerMap.wrapInTrialClass(Wrap.methodWrap(compileSource));
495                break;
496        }
497        snip.setOuterWrap(outer);
498
499        return singletonList(snip);
500    }
501
502    private ExpressionInfo typeOfExpression(String expression) {
503        Wrap guts = Wrap.methodReturnWrap(expression);
504        TaskFactory.AnalyzeTask at = trialCompile(guts);
505        if (!at.hasErrors() && at.firstCuTree() != null) {
506            return TreeDissector.createByFirstClass(at)
507                    .typeOfReturnStatement(at, state);
508        }
509        return null;
510    }
511
512    /**
513     * Should a temp var wrap the expression. TODO make this user configurable.
514     *
515     * @param snippetKind
516     * @return
517     */
518    private boolean shouldGenTempVar(SubKind snippetKind) {
519        return snippetKind == SubKind.OTHER_EXPRESSION_SUBKIND;
520    }
521
522    List<SnippetEvent> drop(Snippet si) {
523        Unit c = new Unit(state, si);
524        Set<Unit> outs;
525        if (si instanceof PersistentSnippet) {
526            Set<Unit> ins = c.dependents().collect(toSet());
527            outs = compileAndLoad(ins);
528        } else {
529            outs = Collections.emptySet();
530        }
531        return events(c, outs, null, null);
532    }
533
534    private List<SnippetEvent> declare(Snippet si, DiagList generatedDiagnostics) {
535        Unit c = new Unit(state, si, null, generatedDiagnostics);
536        Set<Unit> ins = new LinkedHashSet<>();
537        ins.add(c);
538        Set<Unit> outs = compileAndLoad(ins);
539
540        if (!si.status().isDefined()
541                && si.diagnostics().isEmpty()
542                && si.unresolved().isEmpty()) {
543            // did not succeed, but no record of it, extract from others
544            si.setDiagnostics(outs.stream()
545                    .flatMap(u -> u.snippet().diagnostics().stream())
546                    .collect(Collectors.toCollection(DiagList::new)));
547        }
548
549        // If appropriate, execute the snippet
550        String value = null;
551        JShellException exception = null;
552        if (si.status().isDefined()) {
553            if (si.isExecutable()) {
554                try {
555                    value = state.executionControl().invoke(si.classFullName(), DOIT_METHOD_NAME);
556                    value = si.subKind().hasValue()
557                            ? expunge(value)
558                            : "";
559                } catch (ResolutionException ex) {
560                    DeclarationSnippet sn = (DeclarationSnippet) state.maps.getSnippetDeadOrAlive(ex.id());
561                    exception = new UnresolvedReferenceException(sn, translateExceptionStack(ex));
562                } catch (UserException ex) {
563                    exception = new EvalException(translateExceptionMessage(ex),
564                            ex.causeExceptionClass(),
565                            translateExceptionStack(ex));
566                } catch (RunException ex) {
567                    // StopException - no-op
568                } catch (InternalException ex) {
569                    state.debug(ex, "invoke");
570                } catch (EngineTerminationException ex) {
571                    state.closeDown();
572                }
573            } else if (si.subKind() == SubKind.VAR_DECLARATION_SUBKIND) {
574                switch (((VarSnippet) si).typeName()) {
575                    case "byte":
576                    case "short":
577                    case "int":
578                    case "long":
579                        value = "0";
580                        break;
581                    case "float":
582                    case "double":
583                        value = "0.0";
584                        break;
585                    case "boolean":
586                        value = "false";
587                        break;
588                    case "char":
589                        value = "''";
590                        break;
591                    default:
592                        value = "null";
593                        break;
594                }
595            }
596        }
597        return events(c, outs, value, exception);
598    }
599
600    private boolean interestingEvent(SnippetEvent e) {
601        return e.isSignatureChange()
602                    || e.causeSnippet() == null
603                    || e.status() != e.previousStatus()
604                    || e.exception() != null;
605    }
606
607    private List<SnippetEvent> events(Unit c, Collection<Unit> outs, String value, JShellException exception) {
608        List<SnippetEvent> events = new ArrayList<>();
609        events.add(c.event(value, exception));
610        events.addAll(outs.stream()
611                .filter(u -> u != c)
612                .map(u -> u.event(null, null))
613                .filter(this::interestingEvent)
614                .collect(Collectors.toList()));
615        events.addAll(outs.stream()
616                .flatMap(u -> u.secondaryEvents().stream())
617                .filter(this::interestingEvent)
618                .collect(Collectors.toList()));
619        //System.err.printf("Events: %s\n", events);
620        return events;
621    }
622
623    private Set<OuterWrap> outerWrapSet(Collection<Unit> units) {
624        return units.stream()
625                .map(u -> u.snippet().outerWrap())
626                .collect(toSet());
627    }
628
629    private Set<Unit> compileAndLoad(Set<Unit> ins) {
630        if (ins.isEmpty()) {
631            return ins;
632        }
633        Set<Unit> replaced = new LinkedHashSet<>();
634        // Loop until dependencies and errors are stable
635        while (true) {
636            state.debug(DBG_GEN, "compileAndLoad  %s\n", ins);
637
638            ins.stream().forEach(u -> u.initialize());
639            ins.stream().forEach(u -> u.setWrap(ins, ins));
640            AnalyzeTask at = state.taskFactory.new AnalyzeTask(outerWrapSet(ins));
641            ins.stream().forEach(u -> u.setDiagnostics(at));
642
643            // corral any Snippets that need it
644            AnalyzeTask cat;
645            if (ins.stream().anyMatch(u -> u.corralIfNeeded(ins))) {
646                // if any were corralled, re-analyze everything
647                cat = state.taskFactory.new AnalyzeTask(outerWrapSet(ins));
648                ins.stream().forEach(u -> u.setCorralledDiagnostics(cat));
649            } else {
650                cat = at;
651            }
652            ins.stream().forEach(u -> u.setStatus(cat));
653            // compile and load the legit snippets
654            boolean success;
655            while (true) {
656                List<Unit> legit = ins.stream()
657                        .filter(u -> u.isDefined())
658                        .collect(toList());
659                state.debug(DBG_GEN, "compileAndLoad ins = %s -- legit = %s\n",
660                        ins, legit);
661                if (legit.isEmpty()) {
662                    // no class files can be generated
663                    success = true;
664                } else {
665                    // re-wrap with legit imports
666                    legit.stream().forEach(u -> u.setWrap(ins, legit));
667
668                    // generate class files for those capable
669                    CompileTask ct = state.taskFactory.new CompileTask(outerWrapSet(legit));
670                    if (!ct.compile()) {
671                        // oy! compile failed because of recursive new unresolved
672                        if (legit.stream()
673                                .filter(u -> u.smashingErrorDiagnostics(ct))
674                                .count() > 0) {
675                            // try again, with the erroreous removed
676                            continue;
677                        } else {
678                            state.debug(DBG_GEN, "Should never happen error-less failure - %s\n",
679                                    legit);
680                        }
681                    }
682
683                    // load all new classes
684                    load(legit.stream()
685                            .flatMap(u -> u.classesToLoad(ct.classList(u.snippet().outerWrap())))
686                            .collect(toSet()));
687                    // attempt to redefine the remaining classes
688                    List<Unit> toReplace = legit.stream()
689                            .filter(u -> !u.doRedefines())
690                            .collect(toList());
691
692                    // prevent alternating redefine/replace cyclic dependency
693                    // loop by replacing all that have been replaced
694                    if (!toReplace.isEmpty()) {
695                        replaced.addAll(toReplace);
696                        replaced.stream().forEach(u -> u.markForReplacement());
697                    }
698
699                    success = toReplace.isEmpty();
700                }
701                break;
702            }
703
704            // add any new dependencies to the working set
705            List<Unit> newDependencies = ins.stream()
706                    .flatMap(u -> u.effectedDependents())
707                    .collect(toList());
708            state.debug(DBG_GEN, "compileAndLoad %s -- deps: %s  success: %s\n",
709                    ins, newDependencies, success);
710            if (!ins.addAll(newDependencies) && success) {
711                // all classes that could not be directly loaded (because they
712                // are new) have been redefined, and no new dependnencies were
713                // identified
714                ins.stream().forEach(u -> u.finish());
715                return ins;
716            }
717        }
718    }
719
720    /**
721     * If there are classes to load, loads by calling the execution engine.
722     * @param classbytecoes names of the classes to load.
723     */
724    private void load(Collection<ClassBytecodes> classbytecoes) {
725        if (!classbytecoes.isEmpty()) {
726            ClassBytecodes[] cbcs = classbytecoes.toArray(new ClassBytecodes[classbytecoes.size()]);
727            try {
728                state.executionControl().load(cbcs);
729                state.classTracker.markLoaded(cbcs);
730            } catch (ClassInstallException ex) {
731                state.classTracker.markLoaded(cbcs, ex.installed());
732            } catch (NotImplementedException ex) {
733                state.debug(ex, "Seriously?!? load not implemented");
734            } catch (EngineTerminationException ex) {
735                state.closeDown();
736            }
737        }
738    }
739
740    private StackTraceElement[] translateExceptionStack(Exception ex) {
741        StackTraceElement[] raw = ex.getStackTrace();
742        int last = raw.length;
743        do {
744            if (last == 0) {
745                last = raw.length - 1;
746                break;
747            }
748        } while (!isWrap(raw[--last]));
749        StackTraceElement[] elems = new StackTraceElement[last + 1];
750        for (int i = 0; i <= last; ++i) {
751            StackTraceElement r = raw[i];
752            OuterSnippetsClassWrap outer = state.outerMap.getOuter(r.getClassName());
753            if (outer != null) {
754                String klass = expunge(r.getClassName());
755                String method = r.getMethodName().equals(DOIT_METHOD_NAME) ? "" : r.getMethodName();
756                int wln = r.getLineNumber() - 1;
757                int line = outer.wrapLineToSnippetLine(wln) + 1;
758                Snippet sn = outer.wrapLineToSnippet(wln);
759                String file = "#" + sn.id();
760                elems[i] = new StackTraceElement(klass, method, file, line);
761            } else if (r.getFileName().equals("<none>")) {
762                elems[i] = new StackTraceElement(r.getClassName(), r.getMethodName(), null, r.getLineNumber());
763            } else {
764                elems[i] = r;
765            }
766        }
767        return elems;
768    }
769
770    private String translateExceptionMessage(Exception ex) {
771        String msg = ex.getMessage();
772        return msg.equals("<none>")
773                ? null
774                : msg;
775    }
776
777    private boolean isWrap(StackTraceElement ste) {
778        return PREFIX_PATTERN.matcher(ste.getClassName()).find();
779    }
780
781    private DiagList modifierDiagnostics(ModifiersTree modtree,
782            final TreeDissector dis, boolean isAbstractProhibited) {
783
784        class ModifierDiagnostic extends Diag {
785
786            final boolean fatal;
787            final String message;
788
789            ModifierDiagnostic(List<Modifier> list, boolean fatal) {
790                this.fatal = fatal;
791                StringBuilder sb = new StringBuilder();
792                for (Modifier mod : list) {
793                    sb.append("'");
794                    sb.append(mod.toString());
795                    sb.append("' ");
796                }
797                String key = (list.size() > 1)
798                        ? fatal
799                            ? "jshell.diag.modifier.plural.fatal"
800                            : "jshell.diag.modifier.plural.ignore"
801                        : fatal
802                            ? "jshell.diag.modifier.single.fatal"
803                            : "jshell.diag.modifier.single.ignore";
804                this.message = state.messageFormat(key, sb.toString());
805            }
806
807            @Override
808            public boolean isError() {
809                return fatal;
810            }
811
812            @Override
813            public long getPosition() {
814                return dis.getStartPosition(modtree);
815            }
816
817            @Override
818            public long getStartPosition() {
819                return dis.getStartPosition(modtree);
820            }
821
822            @Override
823            public long getEndPosition() {
824                return dis.getEndPosition(modtree);
825            }
826
827            @Override
828            public String getCode() {
829                return fatal
830                        ? "jdk.eval.error.illegal.modifiers"
831                        : "jdk.eval.warn.illegal.modifiers";
832            }
833
834            @Override
835            public String getMessage(Locale locale) {
836                return message;
837            }
838        }
839
840        List<Modifier> list = new ArrayList<>();
841        boolean fatal = false;
842        for (Modifier mod : modtree.getFlags()) {
843            switch (mod) {
844                case SYNCHRONIZED:
845                case NATIVE:
846                    list.add(mod);
847                    fatal = true;
848                    break;
849                case ABSTRACT:
850                    if (isAbstractProhibited) {
851                        list.add(mod);
852                        fatal = true;
853                    }
854                    break;
855                case PUBLIC:
856                case PROTECTED:
857                case PRIVATE:
858                case STATIC:
859                case FINAL:
860                    list.add(mod);
861                    break;
862            }
863        }
864        return list.isEmpty()
865                ? new DiagList()
866                : new DiagList(new ModifierDiagnostic(list, fatal));
867    }
868
869}
870