/* * Copyright (c) 2014, 2021, 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;
// for uses that should not change state -- non-evaluations privateboolean preserveState = false;
privateint varNumber = 0;
/* The number of anonymous innerclasses seen so far. Used to generate unique * names of these classes.
*/ privateint anonCount = 0;
privatefinal JShell state;
// The set of names of methods on Object privatefinal 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; return sourceToSnippets(userSource);
} 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();
/** * Print string, DO NOT replacing all non-ascii character with unicode * escapes.
*/
@Override publicvoid 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) { thrownew 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;
/**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;
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());
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;
}
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;
private Kind kindOfTree(Tree tree) { switch (tree.getKind()) { caseIMPORT: return Kind.IMPORT; case VARIABLE: return Kind.VAR; case EXPRESSION_STATEMENT: return Kind.EXPRESSION; caseCLASS: caseENUM: case ANNOTATION_TYPE: caseINTERFACE: 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);
/** * Should a temp var wrap the expression. TODO make this user configurable. * * @param snippetKind * @return
*/ privateboolean 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)));
}
// Convert an internal UserException to an API EvalException, translating // the stack to snippet form. Convert any chained exceptions private EvalException asEvalException(UserException ue) { returnnew 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()); returnnew UnresolvedReferenceException(sn, translateExceptionStack(re));
}
// Convert an internal UserException/ResolutionException to an API // EvalException/UnresolvedReferenceException private JShellException asJShellException(Throwable e) { if (e == null) { returnnull;
} elseif (e instanceof UserException) { return asEvalException((UserException) e);
} elseif (e instanceof ResolutionException) { return asUnresolvedReferenceException((ResolutionException) e);
} else { thrownew AssertionError(e);
}
}
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();
}.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()));
}
}
} returnnull;
});
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)); returnnull;
});
} else {
ins.forEach(u -> u.setStatus(at));
} returnnull;
}); // 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));
}
// 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.
*/ privatevoid 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);
} elseif ("".equals(r.getFileName())) {
elems[i] = new StackTraceElement(r.getClassName(), r.getMethodName(), null, r.getLineNumber());
} else {
elems[i] = r;
}
} return elems;
}
/** * 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) { returnnew Diag() {
@Override publicboolean isError() { returntrue;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.