langtools/src/jdk.jshell/share/classes/jdk/jshell/Eval.java
author vasya
Mon, 14 Dec 2015 20:18:19 +0100
changeset 34752 9c262a013456
parent 33362 65ec6de1d6b4
child 34857 14d1224cfed3
permissions -rw-r--r--
8145342: Some copyright notices are inconsistently and ill formatted Summary: Adjusted formatting of copyright notices. Reviewed-by: darcy, alundblad

/*
 * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package jdk.jshell;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.lang.model.element.Modifier;
import com.sun.source.tree.ArrayTypeTree;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ModifiersTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.Pretty;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.LinkedHashSet;
import java.util.Set;
import jdk.jshell.ClassTracker.ClassInfo;
import jdk.jshell.Key.ErroneousKey;
import jdk.jshell.Key.MethodKey;
import jdk.jshell.Snippet.SubKind;
import jdk.jshell.TaskFactory.AnalyzeTask;
import jdk.jshell.TaskFactory.BaseTask;
import jdk.jshell.TaskFactory.CompileTask;
import jdk.jshell.TaskFactory.ParseTask;
import jdk.jshell.TreeDissector.ExpressionInfo;
import jdk.jshell.Wrap.Range;
import jdk.jshell.Snippet.Status;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN;
import static jdk.jshell.Util.*;
import static jdk.internal.jshell.remote.RemoteCodes.DOIT_METHOD_NAME;
import static jdk.internal.jshell.remote.RemoteCodes.prefixPattern;
import static jdk.jshell.Snippet.SubKind.SINGLE_TYPE_IMPORT_SUBKIND;
import static jdk.jshell.Snippet.SubKind.SINGLE_STATIC_IMPORT_SUBKIND;
import static jdk.jshell.Snippet.SubKind.TYPE_IMPORT_ON_DEMAND_SUBKIND;
import static jdk.jshell.Snippet.SubKind.STATIC_IMPORT_ON_DEMAND_SUBKIND;

/**
 * The Evaluation Engine. Source internal analysis, wrapping control,
 * compilation, declaration. redefinition, replacement, and execution.
 *
 * @author Robert Field
 */
class Eval {

    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}_\\$]+|\\*))");

    private int varNumber = 0;

    private final JShell state;

    Eval(JShell state) {
        this.state = state;
    }

    List<SnippetEvent> eval(String userSource) throws IllegalStateException {
        String compileSource = Util.trimEnd(new MaskCommentsAndModifiers(userSource, false).cleared());
        if (compileSource.length() == 0) {
            return Collections.emptyList();
        }
        // String folding messes up position information.
        ParseTask pt = state.taskFactory.new ParseTask(compileSource);
        if (pt.getDiagnostics().hasOtherThanNotStatementErrors()) {
            return compileFailResult(pt, userSource);
        }

        List<? extends Tree> units = pt.units();
        if (units.isEmpty()) {
            return compileFailResult(pt, userSource);
        }
        // Erase illegal modifiers
        compileSource = new MaskCommentsAndModifiers(compileSource, true).cleared();
        Tree unitTree = units.get(0);
        state.debug(DBG_GEN, "Kind: %s -- %s\n", unitTree.getKind(), unitTree);
        switch (unitTree.getKind()) {
            case IMPORT:
                return processImport(userSource, compileSource);
            case VARIABLE:
                return processVariables(userSource, units, compileSource, pt);
            case EXPRESSION_STATEMENT:
                return processExpression(userSource, compileSource);
            case CLASS:
                return processClass(userSource, unitTree, compileSource, SubKind.CLASS_SUBKIND, pt);
            case ENUM:
                return processClass(userSource, unitTree, compileSource, SubKind.ENUM_SUBKIND, pt);
            case ANNOTATION_TYPE:
                return processClass(userSource, unitTree, compileSource, SubKind.ANNOTATION_TYPE_SUBKIND, pt);
            case INTERFACE:
                return processClass(userSource, unitTree, compileSource, SubKind.INTERFACE_SUBKIND, pt);
            case METHOD:
                return processMethod(userSource, unitTree, compileSource, pt);
            default:
                return processStatement(userSource, compileSource);
        }
    }

    private List<SnippetEvent> processImport(String userSource, String compileSource) {
        Wrap guts = Wrap.importWrap(compileSource);
        Matcher mat = IMPORT_PATTERN.matcher(compileSource);
        String fullname;
        String name;
        boolean isStatic;
        if (mat.find()) {
            isStatic = mat.group("static") != null;
            name = mat.group("name");
            fullname = mat.group("fullname");
        } else {
            // bad import -- fake it
            isStatic = compileSource.contains("static");
            name = fullname = compileSource;
        }
        String fullkey = (isStatic ? "static-" : "") + fullname;
        boolean isStar = name.equals("*");
        String keyName = isStar
                ? fullname
                : name;
        SubKind snippetKind = isStar
                ? (isStatic ? STATIC_IMPORT_ON_DEMAND_SUBKIND : TYPE_IMPORT_ON_DEMAND_SUBKIND)
                : (isStatic ? SINGLE_STATIC_IMPORT_SUBKIND : SINGLE_TYPE_IMPORT_SUBKIND);
        Snippet snip = new ImportSnippet(state.keyMap.keyForImport(keyName, snippetKind),
                userSource, guts, fullname, name, snippetKind, fullkey, isStatic, isStar);
        return declare(snip);
    }

    private static class EvalPretty extends Pretty {

        private final Writer out;

        public EvalPretty(Writer writer, boolean bln) {
            super(writer, bln);
            this.out = writer;
        }

        /**
         * Print string, DO NOT replacing all non-ascii character with unicode
         * escapes.
         */
        @Override
        public void print(Object o) throws IOException {
            out.write(o.toString());
        }

        static String prettyExpr(JCTree tree, boolean bln) {
            StringWriter out = new StringWriter();
            try {
                new EvalPretty(out, bln).printExpr(tree);
            } catch (IOException e) {
                throw new AssertionError(e);
            }
            return out.toString();
        }
    }

    private List<SnippetEvent> processVariables(String userSource, List<? extends Tree> units, String compileSource, ParseTask pt) {
        List<SnippetEvent> allEvents = new ArrayList<>();
        TreeDissector dis = new TreeDissector(pt);
        for (Tree unitTree : units) {
            VariableTree vt = (VariableTree) unitTree;
            String name = vt.getName().toString();
            String typeName = EvalPretty.prettyExpr((JCTree) vt.getType(), false);
            Tree baseType = vt.getType();
            TreeDependencyScanner tds = new TreeDependencyScanner();
            tds.scan(baseType); // Not dependent on initializer
            StringBuilder sbBrackets = new StringBuilder();
            while (baseType instanceof ArrayTypeTree) {
                //TODO handle annotations too
                baseType = ((ArrayTypeTree) baseType).getType();
                sbBrackets.append("[]");
            }
            Range rtype = dis.treeToRange(baseType);
            Range runit = dis.treeToRange(vt);
            runit = new Range(runit.begin, runit.end - 1);
            ExpressionTree it = vt.getInitializer();
            Range rinit = null;
            int nameMax = runit.end - 1;
            SubKind subkind;
            if (it != null) {
                subkind = SubKind.VAR_DECLARATION_WITH_INITIALIZER_SUBKIND;
                rinit = dis.treeToRange(it);
                nameMax = rinit.begin - 1;
            } else {
                subkind = SubKind.VAR_DECLARATION_SUBKIND;
            }
            int nameStart = compileSource.lastIndexOf(name, nameMax);
            if (nameStart < 0) {
                throw new AssertionError("Name '" + name + "' not found");
            }
            int nameEnd = nameStart + name.length();
            Range rname = new Range(nameStart, nameEnd);
            Wrap guts = Wrap.varWrap(compileSource, rtype, sbBrackets.toString(), rname, rinit);
            Snippet snip = new VarSnippet(state.keyMap.keyForVariable(name), userSource, guts,
                    name, subkind, typeName,
                    tds.declareReferences());
            DiagList modDiag = modifierDiagnostics(vt.getModifiers(), dis, true);
            List<SnippetEvent> res1 = declare(snip, modDiag);
            allEvents.addAll(res1);
        }

        return allEvents;
    }

    private List<SnippetEvent> processExpression(String userSource, String compileSource) {
        String name = null;
        ExpressionInfo ei = typeOfExpression(compileSource);
        ExpressionTree assignVar;
        Wrap guts;
        Snippet snip;
        if (ei != null && ei.isNonVoid) {
            String typeName = ei.typeName;
            SubKind subkind;
            if (ei.tree instanceof IdentifierTree) {
                IdentifierTree id = (IdentifierTree) ei.tree;
                name = id.getName().toString();
                subkind = SubKind.VAR_VALUE_SUBKIND;

            } else if (ei.tree instanceof AssignmentTree
                    && (assignVar = ((AssignmentTree) ei.tree).getVariable()) instanceof IdentifierTree) {
                name = assignVar.toString();
                subkind = SubKind.ASSIGNMENT_SUBKIND;
            } else {
                subkind = SubKind.OTHER_EXPRESSION_SUBKIND;
            }
            if (shouldGenTempVar(subkind)) {
                if (state.tempVariableNameGenerator != null) {
                    name = state.tempVariableNameGenerator.get();
                }
                while (name == null || state.keyMap.doesVariableNameExist(name)) {
                    name = "$" + ++varNumber;
                }
                guts = Wrap.tempVarWrap(compileSource, typeName, name);
                Collection<String> declareReferences = null; //TODO
                snip = new VarSnippet(state.keyMap.keyForVariable(name), userSource, guts,
                        name, SubKind.TEMP_VAR_EXPRESSION_SUBKIND, typeName, declareReferences);
            } else {
                guts = Wrap.methodReturnWrap(compileSource);
                snip = new ExpressionSnippet(state.keyMap.keyForExpression(name, typeName), userSource, guts,
                        name, subkind);
            }
        } else {
            guts = Wrap.methodWrap(compileSource);
            if (ei == null) {
                // We got no type info, check for not a statement by trying
                AnalyzeTask at = trialCompile(guts);
                if (at.getDiagnostics().hasNotStatement()) {
                    guts = Wrap.methodReturnWrap(compileSource);
                    at = trialCompile(guts);
                }
                if (at.hasErrors()) {
                    return compileFailResult(at, userSource);
                }
            }
            snip = new StatementSnippet(state.keyMap.keyForStatement(), userSource, guts);
        }
        return declare(snip);
    }

    private List<SnippetEvent> processClass(String userSource, Tree unitTree, String compileSource, SubKind snippetKind, ParseTask pt) {
        TreeDependencyScanner tds = new TreeDependencyScanner();
        tds.scan(unitTree);

        TreeDissector dis = new TreeDissector(pt);

        ClassTree klassTree = (ClassTree) unitTree;
        String name = klassTree.getSimpleName().toString();
        Wrap guts = Wrap.classMemberWrap(compileSource);
        Wrap corralled = null; //TODO
        Snippet snip = new TypeDeclSnippet(state.keyMap.keyForClass(name), userSource, guts,
                name, snippetKind,
                corralled, tds.declareReferences(), tds.bodyReferences());
        DiagList modDiag = modifierDiagnostics(klassTree.getModifiers(), dis, false);
        return declare(snip, modDiag);
    }

    private List<SnippetEvent> processStatement(String userSource, String compileSource) {
        Wrap guts = Wrap.methodWrap(compileSource);
        // Check for unreachable by trying
        AnalyzeTask at = trialCompile(guts);
        if (at.hasErrors()) {
            if (at.getDiagnostics().hasUnreachableError()) {
                guts = Wrap.methodUnreachableSemiWrap(compileSource);
                at = trialCompile(guts);
                if (at.hasErrors()) {
                    if (at.getDiagnostics().hasUnreachableError()) {
                        // Without ending semicolon
                        guts = Wrap.methodUnreachableWrap(compileSource);
                        at = trialCompile(guts);
                    }
                    if (at.hasErrors()) {
                        return compileFailResult(at, userSource);
                    }
                }
            } else {
                return compileFailResult(at, userSource);
            }
        }
        Snippet snip = new StatementSnippet(state.keyMap.keyForStatement(), userSource, guts);
        return declare(snip);
    }

    private OuterWrap wrapInClass(String className, Set<Key> except, String userSource, Wrap guts, Collection<Snippet> plus) {
        String imports = state.maps.packageAndImportsExcept(except, plus);
        return OuterWrap.wrapInClass(state.maps.packageName(), className, imports, userSource, guts);
    }

    OuterWrap wrapInClass(Snippet snip, Set<Key> except, Wrap guts, Collection<Snippet> plus) {
        return wrapInClass(snip.className(), except, snip.source(), guts, plus);
    }

    private AnalyzeTask trialCompile(Wrap guts) {
        OuterWrap outer = wrapInClass(REPL_DOESNOTMATTER_CLASS_NAME,
                Collections.emptySet(), "", guts, null);
        return state.taskFactory.new AnalyzeTask(outer);
    }

    private List<SnippetEvent> processMethod(String userSource, Tree unitTree, String compileSource, ParseTask pt) {
        TreeDependencyScanner tds = new TreeDependencyScanner();
        tds.scan(unitTree);

        MethodTree mt = (MethodTree) unitTree;
        TreeDissector dis = new TreeDissector(pt);
        DiagList modDiag = modifierDiagnostics(mt.getModifiers(), dis, true);
        if (modDiag.hasErrors()) {
            return compileFailResult(modDiag, userSource);
        }
        String unitName = mt.getName().toString();
        Wrap guts = Wrap.classMemberWrap(compileSource);

        Range modRange = dis.treeToRange(mt.getModifiers());
        Range tpRange = dis.treeListToRange(mt.getTypeParameters());
        Range typeRange = dis.treeToRange(mt.getReturnType());
        String name = mt.getName().toString();
        Range paramRange = dis.treeListToRange(mt.getParameters());
        Range throwsRange = dis.treeListToRange(mt.getThrows());

        String parameterTypes
                = mt.getParameters()
                .stream()
                .map(param -> dis.treeToRange(param.getType()).part(compileSource))
                .collect(Collectors.joining(","));
        String signature = "(" + parameterTypes + ")" + typeRange.part(compileSource);

        MethodKey key = state.keyMap.keyForMethod(name, parameterTypes);
        // rewrap with correct Key index
        Wrap corralled = Wrap.corralledMethod(compileSource,
                modRange, tpRange, typeRange, name, paramRange, throwsRange, key.index());
        Snippet snip = new MethodSnippet(key, userSource, guts,
                unitName, signature,
                corralled, tds.declareReferences(), tds.bodyReferences());
        return declare(snip, modDiag);
    }

    /**
     * The snippet has failed, return with the rejected event
     *
     * @param xt the task from which to extract the failure diagnostics
     * @param userSource the incoming bad user source
     * @return a rejected snippet event
     */
    private List<SnippetEvent> compileFailResult(BaseTask xt, String userSource) {
        return compileFailResult(xt.getDiagnostics(), userSource);
    }

    /**
     * The snippet has failed, return with the rejected event
     *
     * @param diags the failure diagnostics
     * @param userSource the incoming bad user source
     * @return a rejected snippet event
     */
    private List<SnippetEvent> compileFailResult(DiagList diags, String userSource) {
        ErroneousKey key = state.keyMap.keyForErroneous();
        Snippet snip = new ErroneousSnippet(key, userSource, null, SubKind.UNKNOWN_SUBKIND);
        snip.setFailed(diags);
        state.maps.installSnippet(snip);
        return Collections.singletonList(new SnippetEvent(
                snip, Status.NONEXISTENT, Status.REJECTED,
                false, null, null, null)
        );
    }

    private ExpressionInfo typeOfExpression(String expression) {
        Wrap guts = Wrap.methodReturnWrap(expression);
        TaskFactory.AnalyzeTask at = trialCompile(guts);
        if (!at.hasErrors() && at.cuTree() != null) {
            return new TreeDissector(at)
                    .typeOfReturnStatement(at.messages(), state.maps::fullClassNameAndPackageToClass);
        }
        return null;
    }

    /**
     * Should a temp var wrap the expression. TODO make this user configurable.
     *
     * @param snippetKind
     * @return
     */
    private boolean shouldGenTempVar(SubKind snippetKind) {
        return snippetKind == SubKind.OTHER_EXPRESSION_SUBKIND;
    }

    List<SnippetEvent> drop(Snippet si) {
        Unit c = new Unit(state, si);

        Set<Unit> ins = c.dependents().collect(toSet());
        Set<Unit> outs = compileAndLoad(ins);

        return events(c, outs, null, null);
    }

    private List<SnippetEvent> declare(Snippet si) {
        return declare(si, new DiagList());
    }

    private List<SnippetEvent> declare(Snippet si, DiagList generatedDiagnostics) {
        Unit c = new Unit(state, si, null, generatedDiagnostics);

        // Ignores duplicates
        //TODO: remove, modify, or move to edit
        if (c.isRedundant()) {
            return Collections.emptyList();
        }

        Set<Unit> ins = new LinkedHashSet<>();
        ins.add(c);
        Set<Unit> outs = compileAndLoad(ins);

        if (!si.status().isDefined
                && si.diagnostics().isEmpty()
                && si.unresolved().isEmpty()) {
            // did not succeed, but no record of it, extract from others
            si.setDiagnostics(outs.stream()
                    .flatMap(u -> u.snippet().diagnostics().stream())
                    .collect(Collectors.toCollection(DiagList::new)));
        }

        // If appropriate, execute the snippet
        String value = null;
        Exception exception = null;
        if (si.isExecutable() && si.status().isDefined) {
            try {
                value = state.executionControl().commandInvoke(state.maps.classFullName(si));
                value = si.subKind().hasValue()
                        ? expunge(value)
                        : "";
            } catch (EvalException ex) {
                exception = translateExecutionException(ex);
            } catch (UnresolvedReferenceException ex) {
                exception = ex;
            }
        }
        return events(c, outs, value, exception);
    }

    private List<SnippetEvent> events(Unit c, Collection<Unit> outs, String value, Exception exception) {
        List<SnippetEvent> events = new ArrayList<>();
        events.add(c.event(value, exception));
        events.addAll(outs.stream()
                .filter(u -> u != c)
                .map(u -> u.event(null, null))
                .collect(Collectors.toList()));
        events.addAll(outs.stream()
                .flatMap(u -> u.secondaryEvents().stream())
                .collect(Collectors.toList()));
        //System.err.printf("Events: %s\n", events);
        return events;
    }

    private Set<Unit> compileAndLoad(Set<Unit> ins) {
        if (ins.isEmpty()) {
            return ins;
        }
        Set<Unit> replaced = new LinkedHashSet<>();
        while (true) {
            state.debug(DBG_GEN, "compileAndLoad  %s\n", ins);

            ins.stream().forEach(u -> u.initialize(ins));
            AnalyzeTask at = state.taskFactory.new AnalyzeTask(ins);
            ins.stream().forEach(u -> u.setDiagnostics(at));
            // corral any Snippets that need it
            if (ins.stream().filter(u -> u.corralIfNeeded(ins)).count() > 0) {
                // if any were corralled, re-analyze everything
                AnalyzeTask cat = state.taskFactory.new AnalyzeTask(ins);
                ins.stream().forEach(u -> u.setCorralledDiagnostics(cat));
            }
            ins.stream().forEach(u -> u.setStatus());
            // compile and load the legit snippets
            boolean success;
            while (true) {
                List<Unit> legit = ins.stream()
                        .filter(u -> u.isDefined())
                        .collect(toList());
                state.debug(DBG_GEN, "compileAndLoad ins = %s -- legit = %s\n",
                        ins, legit);
                if (legit.isEmpty()) {
                    // no class files can be generated
                    success = true;
                } else {
                    // re-wrap with legit imports
                    legit.stream().forEach(u -> u.setWrap(ins, legit));

                    // generate class files for those capable
                    CompileTask ct = state.taskFactory.new CompileTask(legit);
                    if (!ct.compile()) {
                        // oy! compile failed because of recursive new unresolved
                        if (legit.stream()
                                .filter(u -> u.smashingErrorDiagnostics(ct))
                                .count() > 0) {
                            // try again, with the erroreous removed
                            continue;
                        } else {
                            state.debug(DBG_GEN, "Should never happen error-less failure - %s\n",
                                    legit);
                        }
                    }

                    // load all new classes
                    load(legit.stream()
                            .flatMap(u -> u.classesToLoad(ct.classInfoList(u)))
                            .collect(toList()));
                    // attempt to redefine the remaining classes
                    List<Unit> toReplace = legit.stream()
                            .filter(u -> !u.doRedefines())
                            .collect(toList());

                    // prevent alternating redefine/replace cyclic dependency
                    // loop by replacing all that have been replaced
                    if (!toReplace.isEmpty()) {
                        replaced.addAll(toReplace);
                        replaced.stream().forEach(u -> u.markForReplacement());
                    }

                    success = toReplace.isEmpty();
                }
                break;
            }

            // add any new dependencies to the working set
            List<Unit> newDependencies = ins.stream()
                    .flatMap(u -> u.effectedDependents())
                    .collect(toList());
            state.debug(DBG_GEN, "compileAndLoad %s -- deps: %s  success: %s\n",
                    ins, newDependencies, success);
            if (!ins.addAll(newDependencies) && success) {
                // all classes that could not be directly loaded (because they
                // are new) have been redefined, and no new dependnencies were
                // identified
                ins.stream().forEach(u -> u.finish());
                return ins;
            }
        }
    }

    private void load(List<ClassInfo> cil) {
        if (!cil.isEmpty()) {
            state.executionControl().commandLoad(cil);
        }
    }

    private EvalException translateExecutionException(EvalException ex) {
        StackTraceElement[] raw = ex.getStackTrace();
        int last = raw.length;
        do {
            if (last == 0) {
                last = raw.length - 1;
                break;
            }
        } while (!isWrap(raw[--last]));
        StackTraceElement[] elems = new StackTraceElement[last + 1];
        for (int i = 0; i <= last; ++i) {
            StackTraceElement r = raw[i];
            String rawKlass = r.getClassName();
            Matcher matcher = prefixPattern.matcher(rawKlass);
            String num;
            if (matcher.find() && (num = matcher.group("num")) != null) {
                int end = matcher.end();
                if (rawKlass.charAt(end - 1) == '$') {
                    --end;
                }
                int id = Integer.parseInt(num);
                Snippet si = state.maps.getSnippet(id);
                String klass = expunge(rawKlass);
                String method = r.getMethodName().equals(DOIT_METHOD_NAME) ? "" : r.getMethodName();
                String file = "#" + id;
                int line = si.outerWrap().wrapLineToSnippetLine(r.getLineNumber() - 1) + 1;
                elems[i] = new StackTraceElement(klass, method, file, line);
            } else if (r.getFileName().equals("<none>")) {
                elems[i] = new StackTraceElement(r.getClassName(), r.getMethodName(), null, r.getLineNumber());
            } else {
                elems[i] = r;
            }
        }
        String msg = ex.getMessage();
        if (msg.equals("<none>")) {
            msg = null;
        }
        return new EvalException(msg, ex.getExceptionClassName(), elems);
    }

    private boolean isWrap(StackTraceElement ste) {
        return prefixPattern.matcher(ste.getClassName()).find();
    }

    private DiagList modifierDiagnostics(ModifiersTree modtree,
            final TreeDissector dis, boolean isAbstractProhibited) {

        class ModifierDiagnostic extends Diag {

            final boolean fatal;
            final String message;

            ModifierDiagnostic(List<Modifier> list, boolean fatal) {
                this.fatal = fatal;
                StringBuilder sb = new StringBuilder();
                sb.append((list.size() > 1) ? "Modifiers " : "Modifier ");
                for (Modifier mod : list) {
                    sb.append("'");
                    sb.append(mod.toString());
                    sb.append("' ");
                }
                sb.append("not permitted in top-level declarations");
                if (!fatal) {
                    sb.append(", ignored");
                }
                this.message = sb.toString();
            }

            @Override
            public boolean isError() {
                return fatal;
            }

            @Override
            public long getPosition() {
                return dis.getStartPosition(modtree);
            }

            @Override
            public long getStartPosition() {
                return dis.getStartPosition(modtree);
            }

            @Override
            public long getEndPosition() {
                return dis.getEndPosition(modtree);
            }

            @Override
            public String getCode() {
                return fatal
                        ? "jdk.eval.error.illegal.modifiers"
                        : "jdk.eval.warn.illegal.modifiers";
            }

            @Override
            public String getMessage(Locale locale) {
                return message;
            }

            @Override
            Unit unitOrNull() {
                return null;
            }
        }

        List<Modifier> list = new ArrayList<>();
        boolean fatal = false;
        for (Modifier mod : modtree.getFlags()) {
            switch (mod) {
                case SYNCHRONIZED:
                case NATIVE:
                    list.add(mod);
                    fatal = true;
                    break;
                case ABSTRACT:
                    if (isAbstractProhibited) {
                        list.add(mod);
                        fatal = true;
                    }
                    break;
                case PUBLIC:
                case PROTECTED:
                case PRIVATE:
                case STATIC:
                case FINAL:
                    list.add(mod);
                    break;
            }
        }
        return list.isEmpty()
                ? new DiagList()
                : new DiagList(new ModifierDiagnostic(list, fatal));
    }

}