| /* | |
| * Copyright (c) 2014, 2024, 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.*; | |
| import java.util.regex.Matcher; | |
| import java.util.regex.Pattern; | |
| import java.util.stream.Collectors; | |
| import javax.lang.model.element.Modifier; | |
| import javax.lang.model.element.Name; | |
| import com.sun.source.tree.ArrayTypeTree; | |
| import com.sun.source.tree.AssignmentTree; | |
| import com.sun.source.tree.ClassTree; | |
| import com.sun.source.tree.CompilationUnitTree; | |
| import com.sun.source.tree.ExpressionStatementTree; | |
| import com.sun.source.tree.ExpressionTree; | |
| import com.sun.source.tree.IdentifierTree; | |
| import com.sun.source.tree.MethodInvocationTree; | |
| import com.sun.source.tree.MethodTree; | |
| import com.sun.source.tree.ModifiersTree; | |
| import com.sun.source.tree.NewClassTree; | |
| import com.sun.source.tree.Tree; | |
| import com.sun.source.tree.VariableTree; | |
| import com.sun.source.util.TreePathScanner; | |
| 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 jdk.jshell.ExpressionToTypeInfo.ExpressionInfo; | |
| import jdk.jshell.ExpressionToTypeInfo.ExpressionInfo.AnonymousDescription; | |
| import jdk.jshell.ExpressionToTypeInfo.ExpressionInfo.AnonymousDescription.VariableDesc; | |
| import jdk.jshell.Key.ErroneousKey; | |
| import jdk.jshell.Key.MethodKey; | |
| import jdk.jshell.Key.TypeDeclKey; | |
| import jdk.jshell.Snippet.Kind; | |
| import jdk.jshell.Snippet.SubKind; | |
| import jdk.jshell.TaskFactory.AnalyzeTask; | |
| import jdk.jshell.TaskFactory.BaseTask; | |
| import jdk.jshell.TaskFactory.ParseTask; | |
| import jdk.jshell.Util.Pair; | |
| import jdk.jshell.Wrap.CompoundWrap; | |
| import jdk.jshell.Wrap.Range; | |
| import jdk.jshell.Snippet.Status; | |
| import jdk.jshell.spi.ExecutionControl.ClassBytecodes; | |
| import jdk.jshell.spi.ExecutionControl.ClassInstallException; | |
| import jdk.jshell.spi.ExecutionControl.EngineTerminationException; | |
| import jdk.jshell.spi.ExecutionControl.InternalException; | |
| import jdk.jshell.spi.ExecutionControl.NotImplementedException; | |
| import jdk.jshell.spi.ExecutionControl.ResolutionException; | |
| import jdk.jshell.spi.ExecutionControl.RunException; | |
| import jdk.jshell.spi.ExecutionControl.UserException; | |
| import static java.util.stream.Collectors.toSet; | |
| import static java.util.Collections.singletonList; | |
| import com.sun.tools.javac.code.Symbol.TypeSymbol; | |
| import static jdk.internal.jshell.debug.InternalDebugControl.DBG_GEN; | |
| import static jdk.jshell.Snippet.Status.RECOVERABLE_DEFINED; | |
| import static jdk.jshell.Snippet.Status.VALID; | |
| import static jdk.jshell.Util.DOIT_METHOD_NAME; | |
| import static jdk.jshell.Util.PREFIX_PATTERN; | |
| import static jdk.jshell.Util.expunge; | |
| import static jdk.jshell.Snippet.SubKind.MODULE_IMPORT_SUBKIND; | |
| 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}+(?<module>module\\p{javaWhitespace}+)?(?<static>static\\p{javaWhitespace}+)?(?<fullname>[\\p{L}\\p{N}_\\$\\.]+\\.(?<name>[\\p{L}\\p{N}_\\$]+|\\*))"); | |
| private static final Pattern DEFAULT_PREFIX = Pattern.compile("\\p{javaWhitespace}*(default)\\p{javaWhitespace}+"); | |
| // for uses that should not change state -- non-evaluations | |
| private boolean preserveState = false; | |
| private int varNumber = 0; | |
| /* The number of anonymous innerclasses seen so far. Used to generate unique | |
| * names of these classes. | |
| */ | |
| private int anonCount = 0; | |
| private final JShell state; | |
| // The set of names of methods on Object | |
| private final Set<String> objectMethods = Arrays | |
| .stream(Object.class.getMethods()) | |
| .map(m -> m.getName()) | |
| .collect(toSet()); | |
| Eval(JShell state) { | |
| this.state = state; | |
| } | |
| /** | |
| * Evaluates a snippet of source. | |
| * | |
| * @param userSource the source of the snippet | |
| * @return the list of primary and update events | |
| * @throws IllegalStateException | |
| */ | |
| List<SnippetEvent> eval(String userSource) throws IllegalStateException { | |
| List<SnippetEvent> allEvents = new ArrayList<>(); | |
| for (Snippet snip : sourceToSnippets(userSource)) { | |
| if (snip.kind() == Kind.ERRONEOUS) { | |
| state.maps.installSnippet(snip); | |
| allEvents.add(new SnippetEvent( | |
| snip, Status.NONEXISTENT, Status.REJECTED, | |
| false, null, null, null)); | |
| } else { | |
| allEvents.addAll(declare(snip, snip.syntheticDiags())); | |
| } | |
| } | |
| return allEvents; | |
| } | |
| /** | |
| * Converts the user source of a snippet into a Snippet list -- Snippet will | |
| * have wrappers. | |
| * | |
| * @param userSource the source of the snippet | |
| * @return usually a singleton list of Snippet, but may be empty or multiple | |
| */ | |
| List<Snippet> sourceToSnippetsWithWrappers(String userSource) { | |
| List<Snippet> snippets = sourceToSnippets(userSource); | |
| for (Snippet snip : snippets) { | |
| if (snip.outerWrap() == null) { | |
| snip.setOuterWrap( | |
| (snip.kind() == Kind.IMPORT) | |
| ? state.outerMap.wrapImport(snip.guts(), snip) | |
| : state.outerMap.wrapInTrialClass(snip.guts()) | |
| ); | |
| } | |
| } | |
| return snippets; | |
| } | |
| /** | |
| * Converts the user source of a snippet into a Snippet object (or list of | |
| * objects in the case of: int x, y, z;). Does not install the Snippets | |
| * or execute them. Does not change any state. | |
| * | |
| * @param userSource the source of the snippet | |
| * @return usually a singleton list of Snippet, but may be empty or multiple | |
| */ | |
| List<Snippet> toScratchSnippets(String userSource) { | |
| try { | |
| preserveState = true; | |
| List<Snippet> result = sourceToSnippetsWithWrappers(userSource); | |
| result.forEach(snippet -> { | |
| if (snippet.diagnostics() == null || snippet.diagnostics().isEmpty()) { | |
| //if no better diagnostics set yet, do trial compilation, and | |
| //set diagnostic found: | |
| DiagList fullDiagnostics = state.taskFactory.analyze(snippet.outerWrap(), AnalyzeTask::getDiagnostics); | |
| snippet.setDiagnostics(fullDiagnostics); | |
| } | |
| }); | |
| return result; | |
| } finally { | |
| preserveState = false; | |
| } | |
| } | |
| /** | |
| * Converts the user source of a snippet into a Snippet object (or list of | |
| * objects in the case of: int x, y, z;). Does not install the Snippets | |
| * or execute them. | |
| * | |
| * @param userSource the source of the snippet | |
| * @return usually a singleton list of Snippet, but may be empty or multiple | |
| */ | |
| private List<Snippet> sourceToSnippets(String userSource) { | |
| String compileSource = Util.trimEnd(new MaskCommentsAndModifiers(userSource, false).cleared()); | |
| if (compileSource.length() == 0) { | |
| return Collections.emptyList(); | |
| } | |
| return state.taskFactory.parse(compileSource, pt -> { | |
| List<? extends Tree> units = pt.units(); | |
| if (units.isEmpty()) { | |
| return compileFailResult(pt, userSource, Kind.ERRONEOUS); | |
| } | |
| Tree unitTree = units.get(0); | |
| if (pt.getDiagnostics().hasOtherThanNotStatementErrors()) { | |
| Matcher matcher = DEFAULT_PREFIX.matcher(compileSource); | |
| DiagList dlist = matcher.lookingAt() | |
| ? new DiagList(new ModifierDiagnostic(true, | |
| state.messageFormat("jshell.diag.modifier.single.fatal", "'default'"), | |
| matcher.start(1), matcher.end(1))) | |
| : pt.getDiagnostics(); | |
| return compileFailResult(dlist, userSource, kindOfTree(unitTree)); | |
| } | |
| // Erase illegal/ignored modifiers | |
| String compileSourceInt = new MaskCommentsAndModifiers(compileSource, true).cleared(); | |
| state.debug(DBG_GEN, "Kind: %s -- %s\n", unitTree.getKind(), unitTree); | |
| return switch (unitTree.getKind()) { | |
| case IMPORT | |
| -> processImport(userSource, compileSourceInt); | |
| case VARIABLE | |
| -> processVariables(userSource, units, compileSourceInt, pt); | |
| case EXPRESSION_STATEMENT | |
| -> processExpression(userSource, unitTree, compileSourceInt, pt); | |
| case CLASS | |
| -> processClass(userSource, unitTree, compileSourceInt, SubKind.CLASS_SUBKIND, pt); | |
| case ENUM | |
| -> processClass(userSource, unitTree, compileSourceInt, SubKind.ENUM_SUBKIND, pt); | |
| case ANNOTATION_TYPE | |
| -> processClass(userSource, unitTree, compileSourceInt, SubKind.ANNOTATION_TYPE_SUBKIND, pt); | |
| case INTERFACE | |
| -> processClass(userSource, unitTree, compileSourceInt, SubKind.INTERFACE_SUBKIND, pt); | |
| case RECORD | |
| -> processClass(userSource, unitTree, compileSourceInt, SubKind.RECORD_SUBKIND, pt); | |
| case METHOD | |
| -> processMethod(userSource, unitTree, compileSourceInt, pt); | |
| default | |
| -> processStatement(userSource, compileSourceInt); | |
| }; | |
| }); | |
| } | |
| private List<Snippet> processImport(String userSource, String compileSource) { | |
| Wrap guts = Wrap.simpleWrap(compileSource); | |
| Matcher mat = IMPORT_PATTERN.matcher(compileSource); | |
| String fullname; | |
| String name; | |
| boolean isModule; | |
| boolean isStatic; | |
| if (mat.find()) { | |
| isModule = mat.group("module") != null; | |
| isStatic = mat.group("static") != null; | |
| fullname = mat.group("fullname"); | |
| if (isModule) { | |
| name = fullname; | |
| } else { | |
| name = mat.group("name"); | |
| } | |
| } else { | |
| // bad import -- fake it | |
| isModule = compileSource.contains(" module "); | |
| isStatic = compileSource.contains("static"); | |
| name = fullname = compileSource; | |
| } | |
| String fullkey = (isStatic ? "static-" : "") + fullname; | |
| boolean isStar = name.equals("*"); | |
| String keyName = isStar | |
| ? fullname | |
| : name; | |
| SubKind snippetKind; | |
| if (isModule) { | |
| snippetKind = MODULE_IMPORT_SUBKIND; | |
| } else if (isStar) { | |
| snippetKind = isStatic ? STATIC_IMPORT_ON_DEMAND_SUBKIND : TYPE_IMPORT_ON_DEMAND_SUBKIND; | |
| } else { | |
| snippetKind = 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 singletonList(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<Snippet> processVariables(String userSource, List<? extends Tree> units, String compileSource, ParseTask pt) { | |
| List<Snippet> snippets = new ArrayList<>(); | |
| TreeDissector dis = TreeDissector.createByFirstClass(pt); | |
| for (Tree unitTree : units) { | |
| VariableTree vt = (VariableTree) unitTree; | |
| String name = vt.getName().toString(); | |
| // String name = userReadableName(vt.getName(), compileSource); | |
| String typeName; | |
| String fullTypeName; | |
| String displayType; | |
| boolean hasEnhancedType = false; | |
| TreeDependencyScanner tds = new TreeDependencyScanner(); | |
| Wrap typeWrap; | |
| Wrap anonDeclareWrap = null; | |
| Wrap winit = null; | |
| boolean enhancedDesugaring = false; | |
| Set<String> anonymousClasses = Collections.emptySet(); | |
| StringBuilder sbBrackets = new StringBuilder(); | |
| Tree baseType = vt.getType(); | |
| if (baseType != null) { | |
| tds.scan(baseType); // Not dependent on initializer | |
| fullTypeName = displayType = typeName = EvalPretty.prettyExpr((JCTree) vt.getType(), false); | |
| while (baseType instanceof ArrayTypeTree) { | |
| //TODO handle annotations too | |
| baseType = ((ArrayTypeTree) baseType).getType(); | |
| sbBrackets.append("[]"); | |
| } | |
| Range rtype = dis.treeToRange(baseType); | |
| typeWrap = Wrap.rangeWrap(compileSource, rtype); | |
| } else { | |
| DiagList dl = trialCompile(Wrap.methodWrap(compileSource)); | |
| if (dl.hasErrors()) { | |
| return compileFailResult(dl, userSource, kindOfTree(unitTree)); | |
| } | |
| Tree init = vt.getInitializer(); | |
| if (init != null) { | |
| Range rinit = dis.treeToRange(init); | |
| String initCode = rinit.part(compileSource); | |
| ExpressionInfo ei = | |
| ExpressionToTypeInfo.localVariableTypeForInitializer(initCode, state, false); | |
| if (ei != null && ei.declareTypeName != null) { | |
| typeName = ei.declareTypeName; | |
| fullTypeName = ei.fullTypeName; | |
| displayType = ei.displayTypeName; | |
| hasEnhancedType = !typeName.equals(fullTypeName); | |
| enhancedDesugaring = !ei.isPrimitiveType; | |
| Pair<Wrap, Wrap> anonymous2Member = | |
| anonymous2Member(ei, compileSource, rinit, dis, init); | |
| anonDeclareWrap = anonymous2Member.first; | |
| winit = anonymous2Member.second; | |
| anonymousClasses = ei.anonymousClasses.stream().map(ad -> ad.declareTypeName).collect(Collectors.toSet()); | |
| } else { | |
| displayType = fullTypeName = typeName = "java.lang.Object"; | |
| } | |
| tds.scan(init); | |
| } else { | |
| displayType = fullTypeName = typeName = "java.lang.Object"; | |
| } | |
| typeWrap = Wrap.identityWrap(typeName); | |
| } | |
| Range runit = dis.treeToRange(vt); | |
| runit = new Range(runit.begin, runit.end - 1); | |
| ExpressionTree it = vt.getInitializer(); | |
| int nameMax = runit.end - 1; | |
| SubKind subkind; | |
| if (it != null) { | |
| subkind = SubKind.VAR_DECLARATION_WITH_INITIALIZER_SUBKIND; | |
| Range rinit = dis.treeToRange(it); | |
| winit = winit == null ? Wrap.rangeWrap(compileSource, rinit) : winit; | |
| nameMax = rinit.begin - 1; | |
| } else { | |
| String sinit = switch (typeName) { | |
| case "byte", | |
| "short", | |
| "int" -> "0"; | |
| case "long" -> "0L"; | |
| case "float" -> "0.0f"; | |
| case "double" -> "0.0d"; | |
| case "boolean" -> "false"; | |
| case "char" -> "'\\u0000'"; | |
| default -> "null"; | |
| }; | |
| winit = Wrap.simpleWrap(sinit); | |
| subkind = SubKind.VAR_DECLARATION_SUBKIND; | |
| } | |
| Wrap wname; | |
| String fieldName; | |
| if (name.isEmpty()) { | |
| fieldName = "$UNNAMED"; | |
| wname = Wrap.simpleWrap(fieldName); | |
| } else { | |
| fieldName = name; | |
| int nameStart = compileSource.lastIndexOf(name, nameMax); | |
| if (nameStart < 0) { | |
| // the name has been transformed (e.g. unicode). | |
| // Use it directly | |
| wname = Wrap.identityWrap(name); | |
| } else { | |
| int nameEnd = nameStart + name.length(); | |
| Range rname = new Range(nameStart, nameEnd); | |
| wname = new Wrap.RangeWrap(compileSource, rname); | |
| } | |
| } | |
| Wrap guts = Wrap.varWrap(compileSource, typeWrap, sbBrackets.toString(), wname, | |
| winit, enhancedDesugaring, anonDeclareWrap); | |
| DiagList modDiag = modifierDiagnostics(vt.getModifiers(), dis, true); | |
| Snippet snip = new VarSnippet(state.keyMap.keyForVariable(name), userSource, guts, | |
| name, fieldName, subkind, displayType, hasEnhancedType ? fullTypeName : null, anonymousClasses, | |
| tds.declareReferences(), modDiag); | |
| snippets.add(snip); | |
| } | |
| return snippets; | |
| } | |
| private String userReadableName(Name nn, String compileSource) { | |
| String s = nn.toString(); | |
| if (s.length() > 0 && Character.isJavaIdentifierStart(s.charAt(0)) && compileSource.contains(s)) { | |
| return s; | |
| } | |
| String l = nameInUnicode(nn, false); | |
| if (compileSource.contains(l)) { | |
| return l; | |
| } | |
| return nameInUnicode(nn, true); | |
| } | |
| private String nameInUnicode(Name nn, boolean upper) { | |
| return nn.codePoints() | |
| .mapToObj(cp -> (cp > 0x7F) | |
| ? String.format(upper ? "\\u%04X" : "\\u%04x", cp) | |
| : "" + (char) cp) | |
| .collect(Collectors.joining()); | |
| } | |
| /**Convert anonymous classes in "init" to member classes, based | |
| * on the additional information from ExpressionInfo.anonymousClasses. | |
| * | |
| * This means: | |
| * -if the code in the anonymous class captures any variables from the | |
| * enclosing context, create fields for them | |
| * -creating an explicit constructor that: | |
| * --if the new class expression has a base/enclosing expression, make it an | |
| * explicit constructor parameter "encl" and use "encl.super" when invoking | |
| * the supertype constructor | |
| * --if the (used) supertype constructor has any parameters, declare them | |
| * as explicit parameters of the constructor, and pass them to the super | |
| * constructor | |
| * --if the code in the anonymous class captures any variables from the | |
| * enclosing context, make them an explicit paramters of the constructor | |
| * and assign to respective fields. | |
| * --if there are any explicit fields with initializers in the anonymous class, | |
| * move the initializers at the end of the constructor (after the captured fields | |
| * are assigned, so that the initializers of these fields can use them). | |
| * -from the captured variables fields, constructor, and existing members | |
| * (with cleared field initializers), create an explicit class that extends or | |
| * implements the supertype of the anonymous class. | |
| * | |
| * This method returns two wraps: the first contains the class declarations for the | |
| * converted classes, the first one should be used instead of "init" in the variable | |
| * declaration. | |
| */ | |
| private Pair<Wrap, Wrap> anonymous2Member(ExpressionInfo ei, | |
| String compileSource, | |
| Range rinit, | |
| TreeDissector dis, | |
| Tree init) { | |
| List<Wrap> anonymousDeclarations = new ArrayList<>(); | |
| List<Wrap> partitionedInit = new ArrayList<>(); | |
| int lastPos = rinit.begin; | |
| com.sun.tools.javac.util.List<NewClassTree> toConvert = | |
| ExpressionToTypeInfo.listAnonymousClassesToConvert(init); | |
| com.sun.tools.javac.util.List<AnonymousDescription> descriptions = | |
| ei.anonymousClasses; | |
| while (toConvert.nonEmpty() && descriptions.nonEmpty()) { | |
| NewClassTree node = toConvert.head; | |
| AnonymousDescription ad = descriptions.head; | |
| toConvert = toConvert.tail; | |
| descriptions = descriptions.tail; | |
| List<Object> classBodyParts = new ArrayList<>(); | |
| //declarations of the captured variables: | |
| for (VariableDesc vd : ad.capturedVariables) { | |
| classBodyParts.add(vd.type + " " + vd.name + ";\n"); | |
| } | |
| List<Object> constructorParts = new ArrayList<>(); | |
| constructorParts.add(ad.declareTypeName + "("); | |
| String sep = ""; | |
| //add the parameter for the base/enclosing expression, if any: | |
| if (ad.enclosingInstanceType != null) { | |
| constructorParts.add(ad.enclosingInstanceType + " encl"); | |
| sep = ", "; | |
| } | |
| int idx = 0; | |
| //add parameters of the super constructor, if any: | |
| for (String type : ad.parameterTypes) { | |
| constructorParts.add(sep); | |
| constructorParts.add(type + " " + "arg" + idx++); | |
| sep = ", "; | |
| } | |
| //add parameters for the captured variables: | |
| for (VariableDesc vd : ad.capturedVariables) { | |
| constructorParts.add(sep); | |
| constructorParts.add(vd.type + " " + "cap$" + vd.name); | |
| sep = ", "; | |
| } | |
| //construct super constructor call: | |
| if (ad.enclosingInstanceType != null) { | |
| //if there's an enclosing instance, call super on it: | |
| constructorParts.add(") { encl.super ("); | |
| } else { | |
| constructorParts.add(") { super ("); | |
| } | |
| sep = ""; | |
| for (int i = 0; i < idx; i++) { | |
| constructorParts.add(sep); | |
| constructorParts.add("arg" + i); | |
| sep = ", "; | |
| } | |
| constructorParts.add(");"); | |
| //initialize the captured variables: | |
| for (VariableDesc vd : ad.capturedVariables) { | |
| constructorParts.add("this." + vd.name + " = " + "cap$" + vd.name + ";\n"); | |
| } | |
| List<? extends Tree> members = | |
| node.getClassBody().getMembers(); | |
| for (Tree member : members) { | |
| if (member.getKind() == Tree.Kind.VARIABLE) { | |
| VariableTree vt = (VariableTree) member; | |
| if (vt.getInitializer() != null && | |
| !vt.getModifiers().getFlags().contains(Modifier.STATIC)) { | |
| //for instance variables with initializer, explicitly move the initializer | |
| //to the constructor after the captured variables as assigned | |
| //(the initializers would otherwise run too early): | |
| Range wholeVar = dis.treeToRange(vt); | |
| int name = ((JCTree) vt).pos; | |
| classBodyParts.add(new CompoundWrap(Wrap.rangeWrap(compileSource, | |
| new Range(wholeVar.begin, name)), | |
| vt.getName().toString(), | |
| ";\n")); | |
| constructorParts.add(Wrap.rangeWrap(compileSource, | |
| new Range(name, wholeVar.end))); | |
| continue; | |
| } | |
| } | |
| classBodyParts.add(Wrap.rangeWrap(compileSource, | |
| dis.treeToRange(member))); | |
| } | |
| constructorParts.add("}"); | |
| //construct the member class: | |
| classBodyParts.add(new CompoundWrap(constructorParts.toArray())); | |
| Wrap classBodyWrap = new CompoundWrap(classBodyParts.toArray()); | |
| anonymousDeclarations.add(new CompoundWrap("public static class ", ad.declareTypeName, | |
| (ad.isClass ? " extends " : " implements "), | |
| ad.superTypeName, " { ", classBodyWrap, "}")); | |
| //change the new class expression to use the newly created member type: | |
| Range argRange = dis.treeListToRange(node.getArguments()); | |
| Wrap argWrap; | |
| if (argRange != null) { | |
| argWrap = Wrap.rangeWrap(compileSource, argRange); | |
| } else { | |
| argWrap = Wrap.simpleWrap(" "); | |
| } | |
| if (ad.enclosingInstanceType != null) { | |
| //if there's an enclosing expression, set it as the first parameter: | |
| Range enclosingRanges = | |
| dis.treeToRange(node.getEnclosingExpression()); | |
| Wrap enclosingWrap = Wrap.rangeWrap(compileSource, enclosingRanges); | |
| argWrap = argRange != null ? new CompoundWrap(enclosingWrap, | |
| Wrap.simpleWrap(","), | |
| argWrap) | |
| : enclosingWrap; | |
| } | |
| Range current = dis.treeToRange(node); | |
| String capturedArgs; | |
| if (!ad.capturedVariables.isEmpty()) { | |
| capturedArgs = (ad.parameterTypes.isEmpty() ? "" : ", ") + | |
| ad.capturedVariables.stream() | |
| .map(vd -> vd.name) | |
| .collect(Collectors.joining(",")); | |
| } else { | |
| capturedArgs = ""; | |
| } | |
| if (lastPos < current.begin) | |
| partitionedInit.add(Wrap.rangeWrap(compileSource, | |
| new Range(lastPos, current.begin))); | |
| partitionedInit.add(new CompoundWrap("new " + ad.declareTypeName + "(", | |
| argWrap, | |
| capturedArgs, | |
| ")")); | |
| lastPos = current.end; | |
| } | |
| if (lastPos < rinit.end) | |
| partitionedInit.add(Wrap.rangeWrap(compileSource, new Range(lastPos, rinit.end))); | |
| return new Pair<>(new CompoundWrap(anonymousDeclarations.toArray()), | |
| new CompoundWrap(partitionedInit.toArray())); | |
| } | |
| private List<Snippet> processExpression(String userSource, Tree tree, String compileSource, ParseTask pt) { | |
| ExpressionStatementTree expr = (ExpressionStatementTree) tree; | |
| String name = null; | |
| ExpressionInfo ei = ExpressionToTypeInfo.expressionInfo(compileSource, state); | |
| 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 (preserveState) { | |
| name = "$$"; | |
| } else { | |
| if (state.tempVariableNameGenerator != null) { | |
| name = state.tempVariableNameGenerator.get(); | |
| } | |
| while (name == null || state.keyMap.doesVariableNameExist(name)) { | |
| name = "$" + ++varNumber; | |
| } | |
| } | |
| ExpressionInfo varEI = | |
| ExpressionToTypeInfo.localVariableTypeForInitializer(compileSource, state, true); | |
| String declareTypeName; | |
| String fullTypeName; | |
| String displayTypeName; | |
| Set<String> anonymousClasses; | |
| if (varEI != null) { | |
| declareTypeName = varEI.declareTypeName; | |
| fullTypeName = varEI.fullTypeName; | |
| displayTypeName = varEI.displayTypeName; | |
| TreeDissector dis = TreeDissector.createByFirstClass(pt); | |
| Pair<Wrap, Wrap> anonymous2Member = | |
| anonymous2Member(varEI, compileSource, new Range(0, compileSource.length()), dis, expr.getExpression()); | |
| guts = Wrap.tempVarWrap(anonymous2Member.second.wrapped(), declareTypeName, name, anonymous2Member.first); | |
| anonymousClasses = varEI.anonymousClasses.stream().map(ad -> ad.declareTypeName).collect(Collectors.toSet()); | |
| } else { | |
| declareTypeName = ei.accessibleTypeName; | |
| displayTypeName = fullTypeName = typeName; | |
| guts = Wrap.tempVarWrap(compileSource, declareTypeName, name, null); | |
| anonymousClasses = Collections.emptySet(); | |
| } | |
| Collection<String> declareReferences = null; //TODO | |
| snip = new VarSnippet(state.keyMap.keyForVariable(name), userSource, guts, | |
| name, name, SubKind.TEMP_VAR_EXPRESSION_SUBKIND, displayTypeName, fullTypeName, anonymousClasses, declareReferences, null); | |
| } 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 | |
| DiagList dl = trialCompile(guts); | |
| if (dl.hasUnreachableError()) { | |
| guts = Wrap.methodUnreachableWrap(compileSource); | |
| dl = trialCompile(guts); | |
| } | |
| if (dl.hasNotStatement()) { | |
| guts = Wrap.methodReturnWrap(compileSource); | |
| dl = trialCompile(guts); | |
| } | |
| if (dl.hasErrors()) { | |
| return compileFailResult(dl, userSource, Kind.EXPRESSION); | |
| } | |
| } | |
| snip = new StatementSnippet(state.keyMap.keyForStatement(), userSource, guts); | |
| } | |
| return singletonList(snip); | |
| } | |
| private List<Snippet> processClass(String userSource, Tree unitTree, String compileSource, SubKind snippetKind, ParseTask pt) { | |
| TreeDependencyScanner tds = new TreeDependencyScanner(); | |
| tds.scan(unitTree); | |
| TreeDissector dis = TreeDissector.createByFirstClass(pt); | |
| ClassTree klassTree = (ClassTree) unitTree; | |
| // String name = userReadableName(klassTree.getSimpleName(), compileSource); | |
| String name = klassTree.getSimpleName().toString(); | |
| DiagList modDiag = modifierDiagnostics(klassTree.getModifiers(), dis, false); | |
| TypeDeclKey key = state.keyMap.keyForClass(name); | |
| // Corralling | |
| Wrap corralled = new Corraller(dis, key.index(), compileSource).corralType(klassTree); | |
| Wrap guts = Wrap.classMemberWrap(compileSource); | |
| Snippet snip = new TypeDeclSnippet(key, userSource, guts, | |
| name, snippetKind, | |
| corralled, tds.declareReferences(), tds.bodyReferences(), modDiag); | |
| return singletonList(snip); | |
| } | |
| private List<Snippet> processStatement(String userSource, String compileSource) { | |
| Wrap guts = Wrap.methodWrap(compileSource); | |
| // Check for unreachable by trying | |
| DiagList dl = trialCompile(guts); | |
| if (dl.hasErrors()) { | |
| if (dl.hasUnreachableError()) { | |
| guts = Wrap.methodUnreachableSemiWrap(compileSource); | |
| dl = trialCompile(guts); | |
| if (dl.hasErrors()) { | |
| if (dl.hasUnreachableError()) { | |
| // Without ending semicolon | |
| guts = Wrap.methodUnreachableWrap(compileSource); | |
| dl = trialCompile(guts); | |
| } | |
| if (dl.hasErrors()) { | |
| return compileFailResult(dl, userSource, Kind.STATEMENT); | |
| } | |
| } | |
| } else { | |
| return compileFailResult(dl, userSource, Kind.STATEMENT); | |
| } | |
| } | |
| Snippet snip = new StatementSnippet(state.keyMap.keyForStatement(), userSource, guts); | |
| return singletonList(snip); | |
| } | |
| private DiagList trialCompile(Wrap guts) { | |
| OuterWrap outer = state.outerMap.wrapInTrialClass(guts); | |
| return state.taskFactory.analyze(outer, AnalyzeTask::getDiagnostics); | |
| } | |
| private List<Snippet> processMethod(String userSource, Tree unitTree, String compileSource, ParseTask pt) { | |
| TreeDependencyScanner tds = new TreeDependencyScanner(); | |
| tds.scan(unitTree); | |
| final TreeDissector dis = TreeDissector.createByFirstClass(pt); | |
| final MethodTree mt = (MethodTree) unitTree; | |
| //String name = userReadableName(mt.getName(), compileSource); | |
| final String name = mt.getName().toString(); | |
| if (objectMethods.contains(name)) { | |
| // The name matches a method on Object, short of an overhaul, this | |
| // fails, see 8187137. Generate a descriptive error message | |
| // The error position will be the position of the name, find it | |
| long possibleStart = dis.getEndPosition(mt.getReturnType()); | |
| Range possibleRange = new Range((int) possibleStart, | |
| dis.getStartPosition(mt.getBody())); | |
| String possibleNameSection = possibleRange.part(compileSource); | |
| int offset = possibleNameSection.indexOf(name); | |
| long start = offset < 0 | |
| ? possibleStart // something wrong, punt | |
| : possibleStart + offset; | |
| return compileFailResult(new DiagList(objectMethodNameDiag(name, start)), userSource, Kind.METHOD); | |
| } | |
| String parameterTypes | |
| = mt.getParameters() | |
| .stream() | |
| .map(param -> dis.treeToRange(param.getType()).part(compileSource)) | |
| .collect(Collectors.joining(",")); | |
| Tree returnType = mt.getReturnType(); | |
| DiagList modDiag = modifierDiagnostics(mt.getModifiers(), dis, false); | |
| if (modDiag.hasErrors()) { | |
| return compileFailResult(modDiag, userSource, Kind.METHOD); | |
| } | |
| MethodKey key = state.keyMap.keyForMethod(name, parameterTypes); | |
| Wrap corralled; | |
| Wrap guts; | |
| String unresolvedSelf; | |
| if (mt.getModifiers().getFlags().contains(Modifier.ABSTRACT)) { | |
| if (mt.getBody() == null) { | |
| // abstract method -- pre-corral | |
| corralled = null; // no fall-back | |
| guts = new Corraller(dis, key.index(), compileSource).corralMethod(mt); | |
| unresolvedSelf = "method " + name + "(" + parameterTypes + ")"; | |
| } else { | |
| // abstract with body, don't pollute the error message | |
| corralled = null; | |
| guts = Wrap.simpleWrap(compileSource); | |
| unresolvedSelf = null; | |
| } | |
| } else { | |
| // normal method | |
| corralled = new Corraller(dis, key.index(), compileSource).corralMethod(mt); | |
| guts = Wrap.classMemberWrap(compileSource); | |
| unresolvedSelf = null; | |
| } | |
| Range typeRange = dis.treeToRange(returnType); | |
| String signature = "(" + parameterTypes + ")" + typeRange.part(compileSource); | |
| Snippet snip = new MethodSnippet(key, userSource, guts, | |
| name, signature, | |
| corralled, tds.declareReferences(), tds.bodyReferences(), | |
| unresolvedSelf, modDiag); | |
| return singletonList(snip); | |
| } | |
| private Kind kindOfTree(Tree tree) { | |
| switch (tree.getKind()) { | |
| case IMPORT: | |
| return Kind.IMPORT; | |
| case VARIABLE: | |
| return Kind.VAR; | |
| case EXPRESSION_STATEMENT: | |
| return Kind.EXPRESSION; | |
| case CLASS: | |
| case ENUM: | |
| case ANNOTATION_TYPE: | |
| case INTERFACE: | |
| return Kind.TYPE_DECL; | |
| case METHOD: | |
| return Kind.METHOD; | |
| default: | |
| return Kind.STATEMENT; | |
| } | |
| } | |
| /** | |
| * The snippet has failed, return with the rejected snippet | |
| * | |
| * @param xt the task from which to extract the failure diagnostics | |
| * @param userSource the incoming bad user source | |
| * @return a rejected snippet | |
| */ | |
| private List<Snippet> compileFailResult(BaseTask<?> xt, String userSource, Kind probableKind) { | |
| return compileFailResult(xt.getDiagnostics(), userSource, probableKind); | |
| } | |
| /** | |
| * The snippet has failed, return with the rejected snippet | |
| * | |
| * @param diags the failure diagnostics | |
| * @param userSource the incoming bad user source | |
| * @return a rejected snippet | |
| */ | |
| private List<Snippet> compileFailResult(DiagList diags, String userSource, Kind probableKind) { | |
| ErroneousKey key = state.keyMap.keyForErroneous(); | |
| Snippet snip = new ErroneousSnippet(key, userSource, null, | |
| probableKind, SubKind.UNKNOWN_SUBKIND); | |
| snip.setFailed(diags); | |
| // Install wrapper for query by SourceCodeAnalysis.wrapper | |
| String compileSource = Util.trimEnd(new MaskCommentsAndModifiers(userSource, true).cleared()); | |
| OuterWrap outer = switch (probableKind) { | |
| case IMPORT -> state.outerMap.wrapImport(Wrap.simpleWrap(compileSource), snip); | |
| case EXPRESSION -> state.outerMap.wrapInTrialClass(Wrap.methodReturnWrap(compileSource)); | |
| case VAR, | |
| TYPE_DECL, | |
| METHOD -> state.outerMap.wrapInTrialClass(Wrap.classMemberWrap(compileSource)); | |
| default -> state.outerMap.wrapInTrialClass(Wrap.methodWrap(compileSource)); | |
| }; | |
| snip.setOuterWrap(outer); | |
| return singletonList(snip); | |
| } | |
| /** | |
| * 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> outs; | |
| if (si instanceof PersistentSnippet) { | |
| Set<Unit> ins = c.dependents().collect(toSet()); | |
| outs = compileAndLoad(ins); | |
| } else { | |
| outs = Collections.emptySet(); | |
| } | |
| return events(c, outs, null, null); | |
| } | |
| private List<SnippetEvent> declare(Snippet si, DiagList generatedDiagnostics) { | |
| Unit c = new Unit(state, si, null, generatedDiagnostics); | |
| Set<Unit> ins = new LinkedHashSet<>(); | |
| ins.add(c); | |
| Set<Unit> outs = compileAndLoad(ins); | |
| if (si.status().isActive() && si instanceof MethodSnippet) { | |
| // special processing for abstract methods | |
| MethodSnippet msi = (MethodSnippet) si; | |
| String unresolvedSelf = msi.unresolvedSelf; | |
| if (unresolvedSelf != null) { | |
| List<String> unresolved = new ArrayList<>(si.unresolved()); | |
| unresolved.add(unresolvedSelf); | |
| si.setCompilationStatus(si.status() == VALID ? RECOVERABLE_DEFINED : si.status(), | |
| unresolved, si.diagnostics()); | |
| } | |
| } | |
| 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; | |
| JShellException exception = null; | |
| if (si.status().isDefined()) { | |
| if (si.isExecutable()) { | |
| try { | |
| value = state.executionControl().invoke(si.classFullName(), DOIT_METHOD_NAME); | |
| value = si.subKind().hasValue() | |
| ? expunge(value) | |
| : ""; | |
| } catch (ResolutionException ex) { | |
| exception = asUnresolvedReferenceException(ex); | |
| } catch (UserException ex) { | |
| exception = asEvalException(ex); | |
| } catch (RunException ex) { | |
| // StopException - no-op | |
| } catch (InternalException ex) { | |
| state.debug(ex, "invoke"); | |
| } catch (EngineTerminationException ex) { | |
| state.debug(ex, "termination"); | |
| state.closeDown(); | |
| } | |
| } | |
| } | |
| return events(c, outs, value, exception); | |
| } | |
| // Convert an internal UserException to an API EvalException, translating | |
| // the stack to snippet form. Convert any chained exceptions | |
| private EvalException asEvalException(UserException ue) { | |
| return new EvalException(ue.getMessage(), | |
| ue.causeExceptionClass(), | |
| translateExceptionStack(ue), | |
| asJShellException(ue.getCause())); | |
| } | |
| // Convert an internal ResolutionException to an API UnresolvedReferenceException, | |
| // translating the snippet id to snipper and the stack to snippet form | |
| private UnresolvedReferenceException asUnresolvedReferenceException(ResolutionException re) { | |
| DeclarationSnippet sn = (DeclarationSnippet) state.maps.getSnippetDeadOrAlive(re.id()); | |
| return new UnresolvedReferenceException(sn, translateExceptionStack(re)); | |
| } | |
| // Convert an internal UserException/ResolutionException to an API | |
| // EvalException/UnresolvedReferenceException | |
| private JShellException asJShellException(Throwable e) { | |
| if (e == null) { | |
| return null; | |
| } else if (e instanceof UserException) { | |
| return asEvalException((UserException) e); | |
| } else if (e instanceof ResolutionException) { | |
| return asUnresolvedReferenceException((ResolutionException) e); | |
| } else { | |
| throw new AssertionError(e); | |
| } | |
| } | |
| private boolean interestingEvent(SnippetEvent e) { | |
| return e.isSignatureChange() | |
| || e.causeSnippet() == null | |
| || e.status() != e.previousStatus() | |
| || e.exception() != null; | |
| } | |
| private List<SnippetEvent> events(Unit c, Collection<Unit> outs, String value, JShellException 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)) | |
| .filter(this::interestingEvent) | |
| .toList()); | |
| events.addAll(outs.stream() | |
| .flatMap(u -> u.secondaryEvents().stream()) | |
| .filter(this::interestingEvent) | |
| .toList()); | |
| //System.err.printf("Events: %s\n", events); | |
| return events; | |
| } | |
| private Set<OuterWrap> outerWrapSet(Collection<Unit> units) { | |
| return units.stream() | |
| .map(u -> u.snippet().outerWrap()) | |
| .collect(toSet()); | |
| } | |
| private Set<Unit> compileAndLoad(Set<Unit> ins) { | |
| if (ins.isEmpty()) { | |
| return ins; | |
| } | |
| Set<Unit> replaced = new LinkedHashSet<>(); | |
| // Loop until dependencies and errors are stable | |
| while (true) { | |
| state.debug(DBG_GEN, "compileAndLoad %s\n", ins); | |
| ins.forEach(Unit::initialize); | |
| ins.forEach(u -> u.setWrap(ins, ins)); | |
| if (ins.stream().anyMatch(u -> u.snippet().kind() == Kind.METHOD)) { | |
| //if there is any method declaration, check the body of the method for | |
| //invocations of a method of the same name. It may be an invocation of | |
| //an overloaded method, in which case we need to add all the overloads to | |
| //ins, so that they are processed together and can refer to each other: | |
| Set<Unit> overloads = new LinkedHashSet<>(); | |
| Map<OuterWrap, Unit> outter2Unit = new LinkedHashMap<>(); | |
| ins.forEach(u -> outter2Unit.put(u.snippet().outerWrap(), u)); | |
| state.taskFactory.analyze(outter2Unit.keySet(), at -> { | |
| Set<Unit> suspiciousMethodInvocation = new LinkedHashSet<>(); | |
| for (CompilationUnitTree cut : at.cuTrees()) { | |
| Unit unit = outter2Unit.get(at.sourceForFile(cut.getSourceFile())); | |
| String name = unit.snippet().name(); | |
| new TreePathScanner<Void, Void>() { | |
| @Override | |
| public Void visitMethodInvocation(MethodInvocationTree node, Void p) { | |
| if (node.getMethodSelect().getKind() == Tree.Kind.IDENTIFIER && | |
| ((IdentifierTree) node.getMethodSelect()).getName().contentEquals(name)) { | |
| suspiciousMethodInvocation.add(unit); | |
| } | |
| return super.visitMethodInvocation(node, p); | |
| } | |
| }.scan(cut, null); | |
| } | |
| for (Unit source : suspiciousMethodInvocation) { | |
| for (Snippet dep : state.maps.snippetList()) { | |
| if (dep != source.snippet() && dep.status().isActive() && | |
| dep.kind() == Kind.METHOD && | |
| source.snippet().kind() == Kind.METHOD && | |
| dep.name().equals(source.snippet().name())) { | |
| overloads.add(new Unit(state, dep, source.snippet(), new DiagList())); | |
| } | |
| } | |
| } | |
| return null; | |
| }); | |
| if (ins.addAll(overloads)) { | |
| ins.forEach(Unit::initialize); | |
| ins.forEach(u -> u.setWrap(ins, ins)); | |
| } | |
| } | |
| state.taskFactory.analyze(outerWrapSet(ins), at -> { | |
| ins.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 | |
| state.taskFactory.analyze(outerWrapSet(ins), cat -> { | |
| ins.forEach(u -> u.setCorralledDiagnostics(cat)); | |
| ins.forEach(u -> u.setStatus(cat)); | |
| return null; | |
| }); | |
| } else { | |
| ins.forEach(u -> u.setStatus(at)); | |
| } | |
| return null; | |
| }); | |
| // compile and load the legit snippets | |
| boolean success; | |
| while (true) { | |
| List<Unit> legit = ins.stream() | |
| .filter(Unit::isDefined) | |
| .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.forEach(u -> u.setWrap(ins, legit)); | |
| // generate class files for those capable | |
| Result res = state.taskFactory.compile(outerWrapSet(legit), ct -> { | |
| 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 | |
| return Result.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.classList(u.snippet().outerWrap()))) | |
| .collect(toSet())); | |
| // attempt to redefine the remaining classes | |
| List<Unit> toReplace = legit.stream() | |
| .filter(u -> !u.doRedefines()) | |
| .toList(); | |
| // prevent alternating redefine/replace cyclic dependency | |
| // loop by replacing all that have been replaced | |
| if (!toReplace.isEmpty()) { | |
| replaced.addAll(toReplace); | |
| replaced.forEach(Unit::markForReplacement); | |
| //ensure correct classnames are set in the snippets: | |
| replaced.forEach(u -> u.setWrap(ins, legit)); | |
| } | |
| return toReplace.isEmpty() ? Result.SUCESS : Result.FAILURE; | |
| }); | |
| switch (res) { | |
| case CONTINUE: continue; | |
| case SUCESS: success = true; break; | |
| default: | |
| case FAILURE: success = false; break; | |
| } | |
| } | |
| break; | |
| } | |
| // add any new dependencies to the working set | |
| List<Unit> newDependencies = ins.stream() | |
| .flatMap(Unit::effectedDependents) | |
| .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.forEach(Unit::finish); | |
| return ins; | |
| } | |
| } | |
| } | |
| //where: | |
| enum Result {SUCESS, FAILURE, CONTINUE} | |
| /** | |
| * If there are classes to load, loads by calling the execution engine. | |
| * @param classbytecodes names of the classes to load. | |
| */ | |
| private void load(Collection<ClassBytecodes> classbytecodes) { | |
| if (!classbytecodes.isEmpty()) { | |
| ClassBytecodes[] cbcs = classbytecodes.toArray(new ClassBytecodes[classbytecodes.size()]); | |
| try { | |
| state.executionControl().load(cbcs); | |
| state.classTracker.markLoaded(cbcs); | |
| } catch (ClassInstallException ex) { | |
| state.classTracker.markLoaded(cbcs, ex.installed()); | |
| } catch (NotImplementedException ex) { | |
| state.debug(ex, "Seriously?!? load not implemented"); | |
| state.closeDown(); | |
| } catch (EngineTerminationException ex) { | |
| state.closeDown(); | |
| } | |
| } | |
| } | |
| private StackTraceElement[] translateExceptionStack(Exception 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]; | |
| OuterSnippetsClassWrap outer = state.outerMap.getOuter(r.getClassName()); | |
| if (outer != null) { | |
| String klass = expunge(r.getClassName()); | |
| String method = r.getMethodName().equals(DOIT_METHOD_NAME) ? "" : r.getMethodName(); | |
| int wln = r.getLineNumber() - 1; | |
| int line = outer.wrapLineToSnippetLine(wln) + 1; | |
| Snippet sn = outer.wrapLineToSnippet(wln); | |
| String file = "#" + sn.id(); | |
| elems[i] = new StackTraceElement(klass, method, file, line); | |
| } else if ("<none>".equals(r.getFileName())) { | |
| elems[i] = new StackTraceElement(r.getClassName(), r.getMethodName(), null, r.getLineNumber()); | |
| } else { | |
| elems[i] = r; | |
| } | |
| } | |
| return elems; | |
| } | |
| private boolean isWrap(StackTraceElement ste) { | |
| return PREFIX_PATTERN.matcher(ste.getClassName()).find(); | |
| } | |
| /** | |
| * Construct a diagnostic for a method name matching an Object method name | |
| * @param name the method name | |
| * @param nameStart the position within the source of the method name | |
| * @return the generated diagnostic | |
| */ | |
| private Diag objectMethodNameDiag(String name, long nameStart) { | |
| return new Diag() { | |
| @Override | |
| public boolean isError() { | |
| return true; | |
| } | |
| @Override | |
| public long getPosition() { | |
| return nameStart; | |
| } | |
| @Override | |
| public long getStartPosition() { | |
| return nameStart; | |
| } | |
| @Override | |
| public long getEndPosition() { | |
| return nameStart + name.length(); | |
| } | |
| @Override | |
| public String getCode() { | |
| return "jdk.eval.error.object.method"; | |
| } | |
| @Override | |
| public String getMessage(Locale locale) { | |
| return state.messageFormat("jshell.diag.object.method.fatal", | |
| String.join(" ", objectMethods)); | |
| } | |
| }; | |
| } | |
| private class ModifierDiagnostic extends Diag { | |
| final boolean fatal; | |
| final String message; | |
| final long start; | |
| final long end; | |
| ModifierDiagnostic(boolean fatal, | |
| final String message, | |
| long start, | |
| long end) { | |
| this.fatal = fatal; | |
| this.message = message; | |
| this.start = start; | |
| this.end = end; | |
| } | |
| @Override | |
| public boolean isError() { | |
| return fatal; | |
| } | |
| @Override | |
| public long getPosition() { | |
| return start; | |
| } | |
| @Override | |
| public long getStartPosition() { | |
| return start; | |
| } | |
| @Override | |
| public long getEndPosition() { | |
| return end; | |
| } | |
| @Override | |
| public String getCode() { | |
| return fatal | |
| ? "jdk.eval.error.illegal.modifiers" | |
| : "jdk.eval.warn.illegal.modifiers"; | |
| } | |
| @Override | |
| public String getMessage(Locale locale) { | |
| return message; | |
| } | |
| } | |
| private DiagList modifierDiagnostics(ModifiersTree modtree, | |
| final TreeDissector dis, boolean isAbstractProhibited) { | |
| 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: | |
| // for classes, abstract is valid | |
| // for variables, generate an error message | |
| // for methods, we generate a placeholder method | |
| if (isAbstractProhibited) { | |
| list.add(mod); | |
| fatal = true; | |
| } | |
| break; | |
| case PUBLIC: | |
| case PROTECTED: | |
| case PRIVATE: | |
| // quietly ignore, user cannot see effects one way or the other | |
| break; | |
| case FINAL: | |
| //OK to declare an element final | |
| //final classes needed for sealed classes | |
| break; | |
| case STATIC: | |
| // everything is static -- warning just adds noise when pasting | |
| break; | |
| } | |
| } | |
| if (list.isEmpty()) { | |
| return new DiagList(); | |
| } else { | |
| StringBuilder sb = new StringBuilder(); | |
| for (Modifier mod : list) { | |
| sb.append("'"); | |
| sb.append(mod.toString()); | |
| sb.append("' "); | |
| } | |
| String key = (list.size() > 1) | |
| ? fatal | |
| ? "jshell.diag.modifier.plural.fatal" | |
| : "jshell.diag.modifier.plural.ignore" | |
| : fatal | |
| ? "jshell.diag.modifier.single.fatal" | |
| : "jshell.diag.modifier.single.ignore"; | |
| String message = state.messageFormat(key, sb.toString().trim()); | |
| return new DiagList(new ModifierDiagnostic(fatal, message, | |
| dis.getStartPosition(modtree), dis.getEndPosition(modtree))); | |
| } | |
| } | |
| String computeDeclareName(TypeSymbol ts) { | |
| return Util.JSHELL_ANONYMOUS + "$" + Long.toUnsignedString(anonCount++); | |
| } | |
| } |