8186694: JShell: speed-up compilation by reusing compiler instances

Generalizing ReusableContext and using it in JShell to speed up processing.

Reviewed-by: mcimadamore, rfield
This commit is contained in:
Jan Lahoda 2017-09-01 14:04:20 +02:00
parent fcf9b5115d
commit f66b1c7a8b
34 changed files with 1313 additions and 882 deletions

View file

@ -0,0 +1,390 @@
/*
* Copyright (c) 2015, 2017, 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.
*
* 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 com.sun.tools.javac.api;
import java.io.PrintStream;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.util.JavacTask;
import com.sun.source.util.TaskEvent;
import com.sun.source.util.TaskEvent.Kind;
import com.sun.source.util.TaskListener;
import com.sun.source.util.TreeScanner;
import com.sun.tools.javac.code.Kinds;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symtab;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Type.ClassType;
import com.sun.tools.javac.code.TypeTag;
import com.sun.tools.javac.code.Types;
import com.sun.tools.javac.comp.Annotate;
import com.sun.tools.javac.comp.Check;
import com.sun.tools.javac.comp.CompileStates;
import com.sun.tools.javac.comp.Enter;
import com.sun.tools.javac.comp.Modules;
import com.sun.tools.javac.main.Arguments;
import com.sun.tools.javac.main.JavaCompiler;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticListener;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import com.sun.tools.javac.model.JavacElements;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.DefinedBy;
import com.sun.tools.javac.util.DefinedBy.Api;
import com.sun.tools.javac.util.Log;
/**
* A pool of reusable JavacTasks. When a task is no valid anymore, it is returned to the pool,
* and its Context may be reused for future processing in some cases. The reuse is achieved
* by replacing some components (most notably JavaCompiler and Log) with reusable counterparts,
* and by cleaning up leftovers from previous compilation.
* <p>
* For each combination of options, a separate task/context is created and kept, as most option
* values are cached inside components themselves.
* <p>
* When the compilation redefines sensitive classes (e.g. classes in the the java.* packages), the
* task/context is not reused.
* <p>
* When the task is reused, then packages that were already listed won't be listed again.
* <p>
* Care must be taken to only return tasks that won't be used by the original caller.
* <p>
* Care must also be taken when custom components are installed, as those are not cleaned when the
* task/context is reused, and subsequent getTask may return a task based on a context with these
* custom components.
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own risk.
* This code and its internal interfaces are subject to change or
* deletion without notice.</b>
*/
public class JavacTaskPool {
private static final JavacTool systemProvider = JavacTool.create();
private final int maxPoolSize;
private final Map<List<String>, List<ReusableContext>> options2Contexts = new HashMap<>();
private int id;
private int statReused = 0;
private int statNew = 0;
private int statPolluted = 0;
private int statRemoved = 0;
/**Creates the pool.
*
* @param maxPoolSize maximum number of tasks/context that will be kept in the pool.
*/
public JavacTaskPool(int maxPoolSize) {
this.maxPoolSize = maxPoolSize;
}
/**Creates a new task as if by {@link javax.tools.JavaCompiler#getTask} and runs the provided
* worker with it. The task is only valid while the worker is running. The internal structures
* may be reused from some previous compilation.
*
* @param out a Writer for additional output from the compiler;
* use {@code System.err} if {@code null}
* @param fileManager a file manager; if {@code null} use the
* compiler's standard filemanager
* @param diagnosticListener a diagnostic listener; if {@code
* null} use the compiler's default method for reporting
* diagnostics
* @param options compiler options, {@code null} means no options
* @param classes names of classes to be processed by annotation
* processing, {@code null} means no class names
* @param compilationUnits the compilation units to compile, {@code
* null} means no compilation units
* @param worker that should be run with the task
* @return an object representing the compilation
* @throws RuntimeException if an unrecoverable error
* occurred in a user supplied component. The
* {@linkplain Throwable#getCause() cause} will be the error in
* user code.
* @throws IllegalArgumentException if any of the options are invalid,
* or if any of the given compilation units are of other kind than
* {@linkplain JavaFileObject.Kind#SOURCE source}
*/
public <Z> Z getTask(Writer out,
JavaFileManager fileManager,
DiagnosticListener<? super JavaFileObject> diagnosticListener,
Iterable<String> options,
Iterable<String> classes,
Iterable<? extends JavaFileObject> compilationUnits,
Worker<Z> worker) {
List<String> opts =
StreamSupport.stream(options.spliterator(), false)
.collect(Collectors.toCollection(ArrayList::new));
ReusableContext ctx;
synchronized (this) {
List<ReusableContext> cached =
options2Contexts.getOrDefault(opts, Collections.emptyList());
if (cached.isEmpty()) {
ctx = new ReusableContext(opts);
statNew++;
} else {
ctx = cached.remove(0);
statReused++;
}
}
ctx.useCount++;
JavacTaskImpl task =
(JavacTaskImpl) systemProvider.getTask(out, fileManager, diagnosticListener,
opts, classes, compilationUnits, ctx);
task.addTaskListener(ctx);
Z result = worker.withTask(task);
//not returning the context to the pool if task crashes with an exception
//the task/context may be in a broken state
ctx.clear();
if (ctx.polluted) {
statPolluted++;
} else {
task.cleanup();
synchronized (this) {
while (cacheSize() + 1 > maxPoolSize) {
ReusableContext toRemove =
options2Contexts.values()
.stream()
.flatMap(Collection::stream)
.sorted((c1, c2) -> c1.timeStamp < c2.timeStamp ? -1 : 1)
.findFirst()
.get();
options2Contexts.get(toRemove.arguments).remove(toRemove);
statRemoved++;
}
options2Contexts.computeIfAbsent(ctx.arguments, x -> new ArrayList<>()).add(ctx);
ctx.timeStamp = id++;
}
}
return result;
}
//where:
private long cacheSize() {
return options2Contexts.values().stream().flatMap(Collection::stream).count();
}
public void printStatistics(PrintStream out) {
out.println(statReused + " reused Contexts");
out.println(statNew + " newly created Contexts");
out.println(statPolluted + " polluted Contexts");
out.println(statRemoved + " removed Contexts");
}
public interface Worker<Z> {
public Z withTask(JavacTask task);
}
static class ReusableContext extends Context implements TaskListener {
Set<CompilationUnitTree> roots = new HashSet<>();
List<String> arguments;
boolean polluted = false;
int useCount;
long timeStamp;
ReusableContext(List<String> arguments) {
super();
this.arguments = arguments;
put(Log.logKey, ReusableLog.factory);
put(JavaCompiler.compilerKey, ReusableJavaCompiler.factory);
}
void clear() {
drop(Arguments.argsKey);
drop(DiagnosticListener.class);
drop(Log.outKey);
drop(Log.errKey);
drop(JavaFileManager.class);
drop(JavacTask.class);
drop(JavacTrees.class);
drop(JavacElements.class);
if (ht.get(Log.logKey) instanceof ReusableLog) {
//log already inited - not first round
((ReusableLog)Log.instance(this)).clear();
Enter.instance(this).newRound();
((ReusableJavaCompiler)ReusableJavaCompiler.instance(this)).clear();
Types.instance(this).newRound();
Check.instance(this).newRound();
Modules.instance(this).newRound();
Annotate.instance(this).newRound();
CompileStates.instance(this).clear();
MultiTaskListener.instance(this).clear();
//find if any of the roots have redefined java.* classes
Symtab syms = Symtab.instance(this);
pollutionScanner.scan(roots, syms);
roots.clear();
}
}
/**
* This scanner detects as to whether the shared context has been polluted. This happens
* whenever a compiled program redefines a core class (in 'java.*' package) or when
* (typically because of cyclic inheritance) the symbol kind of a core class has been touched.
*/
TreeScanner<Void, Symtab> pollutionScanner = new TreeScanner<Void, Symtab>() {
@Override @DefinedBy(Api.COMPILER_TREE)
public Void visitClass(ClassTree node, Symtab syms) {
Symbol sym = ((JCClassDecl)node).sym;
if (sym != null) {
syms.removeClass(sym.packge().modle, sym.flatName());
Type sup = supertype(sym);
if (isCoreClass(sym) ||
(sup != null && isCoreClass(sup.tsym) && sup.tsym.kind != Kinds.Kind.TYP)) {
polluted = true;
}
}
return super.visitClass(node, syms);
}
private boolean isCoreClass(Symbol s) {
return s.flatName().toString().startsWith("java.");
}
private Type supertype(Symbol s) {
if (s.type == null ||
!s.type.hasTag(TypeTag.CLASS)) {
return null;
} else {
ClassType ct = (ClassType)s.type;
return ct.supertype_field;
}
}
};
@Override @DefinedBy(Api.COMPILER_TREE)
public void finished(TaskEvent e) {
if (e.getKind() == Kind.PARSE) {
roots.add(e.getCompilationUnit());
}
}
@Override @DefinedBy(Api.COMPILER_TREE)
public void started(TaskEvent e) {
//do nothing
}
<T> void drop(Key<T> k) {
ht.remove(k);
}
<T> void drop(Class<T> c) {
ht.remove(key(c));
}
/**
* Reusable JavaCompiler; exposes a method to clean up the component from leftovers associated with
* previous compilations.
*/
static class ReusableJavaCompiler extends JavaCompiler {
final static Factory<JavaCompiler> factory = ReusableJavaCompiler::new;
ReusableJavaCompiler(Context context) {
super(context);
}
@Override
public void close() {
//do nothing
}
void clear() {
newRound();
}
@Override
protected void checkReusable() {
//do nothing - it's ok to reuse the compiler
}
}
/**
* Reusable Log; exposes a method to clean up the component from leftovers associated with
* previous compilations.
*/
static class ReusableLog extends Log {
final static Factory<Log> factory = ReusableLog::new;
Context context;
ReusableLog(Context context) {
super(context);
this.context = context;
}
void clear() {
recorded.clear();
sourceMap.clear();
nerrors = 0;
nwarnings = 0;
//Set a fake listener that will lazily lookup the context for the 'real' listener. Since
//this field is never updated when a new task is created, we cannot simply reset the field
//or keep old value. This is a hack to workaround the limitations in the current infrastructure.
diagListener = new DiagnosticListener<JavaFileObject>() {
DiagnosticListener<JavaFileObject> cachedListener;
@Override @DefinedBy(Api.COMPILER)
@SuppressWarnings("unchecked")
public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
if (cachedListener == null) {
cachedListener = context.get(DiagnosticListener.class);
}
cachedListener.report(diagnostic);
}
};
}
}
}
}

View file

@ -241,12 +241,19 @@ public class TypeEnter implements Completer {
boolean firstToComplete = queue.isEmpty(); boolean firstToComplete = queue.isEmpty();
Phase prevTopLevelPhase = topLevelPhase; Phase prevTopLevelPhase = topLevelPhase;
boolean success = false;
try { try {
topLevelPhase = this; topLevelPhase = this;
doCompleteEnvs(envs); doCompleteEnvs(envs);
success = true;
} finally { } finally {
topLevelPhase = prevTopLevelPhase; topLevelPhase = prevTopLevelPhase;
if (!success && firstToComplete) {
//an exception was thrown, e.g. BreakAttr:
//the queue would become stale, clear it:
queue.clear();
}
} }
if (firstToComplete) { if (firstToComplete) {

View file

@ -45,6 +45,7 @@ import jdk.jshell.SourceCodeAnalysis.Completeness;
import com.sun.source.tree.Tree; import com.sun.source.tree.Tree;
import static jdk.jshell.CompletenessAnalyzer.TK.*; import static jdk.jshell.CompletenessAnalyzer.TK.*;
import jdk.jshell.TaskFactory.ParseTask; import jdk.jshell.TaskFactory.ParseTask;
import jdk.jshell.TaskFactory.Worker;
import java.util.List; import java.util.List;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -85,7 +86,7 @@ class CompletenessAnalyzer {
try { try {
Parser parser = new Parser( Parser parser = new Parser(
() -> new Matched(scannerFactory.newScanner(s, false)), () -> new Matched(scannerFactory.newScanner(s, false)),
() -> proc.taskFactory.parse(s)); worker -> proc.taskFactory.parse(s, worker));
Completeness stat = parser.parseUnit(); Completeness stat = parser.parseUnit();
int endPos = stat == Completeness.UNKNOWN int endPos = stat == Completeness.UNKNOWN
? s.length() ? s.length()
@ -561,12 +562,13 @@ class CompletenessAnalyzer {
private static class Parser { private static class Parser {
private final Supplier<Matched> matchedFactory; private final Supplier<Matched> matchedFactory;
private final Supplier<ParseTask> parseFactory; private final Function<Worker<ParseTask, Completeness>, Completeness> parseFactory;
private Matched in; private Matched in;
private CT token; private CT token;
private Completeness checkResult; private Completeness checkResult;
Parser(Supplier<Matched> matchedFactory, Supplier<ParseTask> parseFactory) { Parser(Supplier<Matched> matchedFactory,
Function<Worker<ParseTask, Completeness>, Completeness> parseFactory) {
this.matchedFactory = matchedFactory; this.matchedFactory = matchedFactory;
this.parseFactory = parseFactory; this.parseFactory = parseFactory;
resetInput(); resetInput();
@ -692,30 +694,31 @@ class CompletenessAnalyzer {
public Completeness disambiguateDeclarationVsExpression() { public Completeness disambiguateDeclarationVsExpression() {
// String folding messes up position information. // String folding messes up position information.
ParseTask pt = parseFactory.get(); return parseFactory.apply(pt -> {
List<? extends Tree> units = pt.units(); List<? extends Tree> units = pt.units();
if (units.isEmpty()) { if (units.isEmpty()) {
return error();
}
Tree unitTree = units.get(0);
switch (unitTree.getKind()) {
case EXPRESSION_STATEMENT:
return parseExpressionOptionalSemi();
case LABELED_STATEMENT:
if (shouldAbort(IDENTIFIER)) return checkResult;
if (shouldAbort(COLON)) return checkResult;
return parseStatement();
case VARIABLE:
case IMPORT:
case CLASS:
case ENUM:
case ANNOTATION_TYPE:
case INTERFACE:
case METHOD:
return parseDeclaration();
default:
return error(); return error();
} }
Tree unitTree = units.get(0);
switch (unitTree.getKind()) {
case EXPRESSION_STATEMENT:
return parseExpressionOptionalSemi();
case LABELED_STATEMENT:
if (shouldAbort(IDENTIFIER)) return checkResult;
if (shouldAbort(COLON)) return checkResult;
return parseStatement();
case VARIABLE:
case IMPORT:
case CLASS:
case ENUM:
case ANNOTATION_TYPE:
case INTERFACE:
case METHOD:
return parseDeclaration();
default:
return error();
}
});
} }
public Completeness parseExpressionStatement() { public Completeness parseExpressionStatement() {

View file

@ -177,40 +177,41 @@ class Eval {
if (compileSource.length() == 0) { if (compileSource.length() == 0) {
return Collections.emptyList(); return Collections.emptyList();
} }
ParseTask pt = state.taskFactory.parse(compileSource); return state.taskFactory.parse(compileSource, pt -> {
List<? extends Tree> units = pt.units(); List<? extends Tree> units = pt.units();
if (units.isEmpty()) { if (units.isEmpty()) {
return compileFailResult(pt, userSource, Kind.ERRONEOUS); return compileFailResult(pt, userSource, Kind.ERRONEOUS);
} }
Tree unitTree = units.get(0); Tree unitTree = units.get(0);
if (pt.getDiagnostics().hasOtherThanNotStatementErrors()) { if (pt.getDiagnostics().hasOtherThanNotStatementErrors()) {
return compileFailResult(pt, userSource, kindOfTree(unitTree)); return compileFailResult(pt, userSource, kindOfTree(unitTree));
} }
// Erase illegal/ignored modifiers // Erase illegal/ignored modifiers
compileSource = new MaskCommentsAndModifiers(compileSource, true).cleared(); String compileSourceInt = new MaskCommentsAndModifiers(compileSource, true).cleared();
state.debug(DBG_GEN, "Kind: %s -- %s\n", unitTree.getKind(), unitTree); state.debug(DBG_GEN, "Kind: %s -- %s\n", unitTree.getKind(), unitTree);
switch (unitTree.getKind()) { switch (unitTree.getKind()) {
case IMPORT: case IMPORT:
return processImport(userSource, compileSource); return processImport(userSource, compileSourceInt);
case VARIABLE: case VARIABLE:
return processVariables(userSource, units, compileSource, pt); return processVariables(userSource, units, compileSourceInt, pt);
case EXPRESSION_STATEMENT: case EXPRESSION_STATEMENT:
return processExpression(userSource, compileSource); return processExpression(userSource, compileSourceInt);
case CLASS: case CLASS:
return processClass(userSource, unitTree, compileSource, SubKind.CLASS_SUBKIND, pt); return processClass(userSource, unitTree, compileSourceInt, SubKind.CLASS_SUBKIND, pt);
case ENUM: case ENUM:
return processClass(userSource, unitTree, compileSource, SubKind.ENUM_SUBKIND, pt); return processClass(userSource, unitTree, compileSourceInt, SubKind.ENUM_SUBKIND, pt);
case ANNOTATION_TYPE: case ANNOTATION_TYPE:
return processClass(userSource, unitTree, compileSource, SubKind.ANNOTATION_TYPE_SUBKIND, pt); return processClass(userSource, unitTree, compileSourceInt, SubKind.ANNOTATION_TYPE_SUBKIND, pt);
case INTERFACE: case INTERFACE:
return processClass(userSource, unitTree, compileSource, SubKind.INTERFACE_SUBKIND, pt); return processClass(userSource, unitTree, compileSourceInt, SubKind.INTERFACE_SUBKIND, pt);
case METHOD: case METHOD:
return processMethod(userSource, unitTree, compileSource, pt); return processMethod(userSource, unitTree, compileSourceInt, pt);
default: default:
return processStatement(userSource, compileSource); return processStatement(userSource, compileSourceInt);
} }
});
} }
private List<Snippet> processImport(String userSource, String compileSource) { private List<Snippet> processImport(String userSource, String compileSource) {
@ -295,9 +296,9 @@ class Eval {
Range rtype = dis.treeToRange(baseType); Range rtype = dis.treeToRange(baseType);
typeWrap = Wrap.rangeWrap(compileSource, rtype); typeWrap = Wrap.rangeWrap(compileSource, rtype);
} else { } else {
AnalyzeTask at = trialCompile(Wrap.methodWrap(compileSource)); DiagList dl = trialCompile(Wrap.methodWrap(compileSource));
if (at.hasErrors()) { if (dl.hasErrors()) {
return compileFailResult(at, userSource, kindOfTree(unitTree)); return compileFailResult(dl, userSource, kindOfTree(unitTree));
} }
Tree init = vt.getInitializer(); Tree init = vt.getInitializer();
if (init != null) { if (init != null) {
@ -459,13 +460,13 @@ class Eval {
guts = Wrap.methodWrap(compileSource); guts = Wrap.methodWrap(compileSource);
if (ei == null) { if (ei == null) {
// We got no type info, check for not a statement by trying // We got no type info, check for not a statement by trying
AnalyzeTask at = trialCompile(guts); DiagList dl = trialCompile(guts);
if (at.getDiagnostics().hasNotStatement()) { if (dl.hasNotStatement()) {
guts = Wrap.methodReturnWrap(compileSource); guts = Wrap.methodReturnWrap(compileSource);
at = trialCompile(guts); dl = trialCompile(guts);
} }
if (at.hasErrors()) { if (dl.hasErrors()) {
return compileFailResult(at, userSource, Kind.EXPRESSION); return compileFailResult(dl, userSource, Kind.EXPRESSION);
} }
} }
snip = new StatementSnippet(state.keyMap.keyForStatement(), userSource, guts); snip = new StatementSnippet(state.keyMap.keyForStatement(), userSource, guts);
@ -496,32 +497,32 @@ class Eval {
private List<Snippet> processStatement(String userSource, String compileSource) { private List<Snippet> processStatement(String userSource, String compileSource) {
Wrap guts = Wrap.methodWrap(compileSource); Wrap guts = Wrap.methodWrap(compileSource);
// Check for unreachable by trying // Check for unreachable by trying
AnalyzeTask at = trialCompile(guts); DiagList dl = trialCompile(guts);
if (at.hasErrors()) { if (dl.hasErrors()) {
if (at.getDiagnostics().hasUnreachableError()) { if (dl.hasUnreachableError()) {
guts = Wrap.methodUnreachableSemiWrap(compileSource); guts = Wrap.methodUnreachableSemiWrap(compileSource);
at = trialCompile(guts); dl = trialCompile(guts);
if (at.hasErrors()) { if (dl.hasErrors()) {
if (at.getDiagnostics().hasUnreachableError()) { if (dl.hasUnreachableError()) {
// Without ending semicolon // Without ending semicolon
guts = Wrap.methodUnreachableWrap(compileSource); guts = Wrap.methodUnreachableWrap(compileSource);
at = trialCompile(guts); dl = trialCompile(guts);
} }
if (at.hasErrors()) { if (dl.hasErrors()) {
return compileFailResult(at, userSource, Kind.STATEMENT); return compileFailResult(dl, userSource, Kind.STATEMENT);
} }
} }
} else { } else {
return compileFailResult(at, userSource, Kind.STATEMENT); return compileFailResult(dl, userSource, Kind.STATEMENT);
} }
} }
Snippet snip = new StatementSnippet(state.keyMap.keyForStatement(), userSource, guts); Snippet snip = new StatementSnippet(state.keyMap.keyForStatement(), userSource, guts);
return singletonList(snip); return singletonList(snip);
} }
private AnalyzeTask trialCompile(Wrap guts) { private DiagList trialCompile(Wrap guts) {
OuterWrap outer = state.outerMap.wrapInTrialClass(guts); OuterWrap outer = state.outerMap.wrapInTrialClass(guts);
return state.taskFactory.new AnalyzeTask(outer); return state.taskFactory.analyze(outer, AnalyzeTask::getDiagnostics);
} }
private List<Snippet> processMethod(String userSource, Tree unitTree, String compileSource, ParseTask pt) { private List<Snippet> processMethod(String userSource, Tree unitTree, String compileSource, ParseTask pt) {
@ -751,19 +752,22 @@ class Eval {
ins.stream().forEach(Unit::initialize); ins.stream().forEach(Unit::initialize);
ins.stream().forEach(u -> u.setWrap(ins, ins)); ins.stream().forEach(u -> u.setWrap(ins, ins));
AnalyzeTask at = state.taskFactory.new AnalyzeTask(outerWrapSet(ins)); state.taskFactory.analyze(outerWrapSet(ins), at -> {
ins.stream().forEach(u -> u.setDiagnostics(at)); ins.stream().forEach(u -> u.setDiagnostics(at));
// corral any Snippets that need it // corral any Snippets that need it
AnalyzeTask cat; if (ins.stream().anyMatch(u -> u.corralIfNeeded(ins))) {
if (ins.stream().anyMatch(u -> u.corralIfNeeded(ins))) { // if any were corralled, re-analyze everything
// if any were corralled, re-analyze everything state.taskFactory.analyze(outerWrapSet(ins), cat -> {
cat = state.taskFactory.new AnalyzeTask(outerWrapSet(ins)); ins.stream().forEach(u -> u.setCorralledDiagnostics(cat));
ins.stream().forEach(u -> u.setCorralledDiagnostics(cat)); ins.stream().forEach(u -> u.setStatus(cat));
} else { return null;
cat = at; });
} } else {
ins.stream().forEach(u -> u.setStatus(cat)); ins.stream().forEach(u -> u.setStatus(at));
}
return null;
});
// compile and load the legit snippets // compile and load the legit snippets
boolean success; boolean success;
while (true) { while (true) {
@ -780,37 +784,45 @@ class Eval {
legit.stream().forEach(u -> u.setWrap(ins, legit)); legit.stream().forEach(u -> u.setWrap(ins, legit));
// generate class files for those capable // generate class files for those capable
CompileTask ct = state.taskFactory.new CompileTask(outerWrapSet(legit)); Result res = state.taskFactory.compile(outerWrapSet(legit), ct -> {
if (!ct.compile()) { if (!ct.compile()) {
// oy! compile failed because of recursive new unresolved // oy! compile failed because of recursive new unresolved
if (legit.stream() if (legit.stream()
.filter(u -> u.smashingErrorDiagnostics(ct)) .filter(u -> u.smashingErrorDiagnostics(ct))
.count() > 0) { .count() > 0) {
// try again, with the erroreous removed // try again, with the erroreous removed
continue; return Result.CONTINUE;
} else { } else {
state.debug(DBG_GEN, "Should never happen error-less failure - %s\n", state.debug(DBG_GEN, "Should never happen error-less failure - %s\n",
legit); 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())
.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(Unit::markForReplacement);
}
return toReplace.isEmpty() ? Result.SUCESS : Result.FAILURE;
});
switch (res) {
case CONTINUE: continue;
case SUCESS: success = true; break;
default:
case FAILURE: success = false; break;
} }
// 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())
.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(Unit::markForReplacement);
}
success = toReplace.isEmpty();
} }
break; break;
} }
@ -830,6 +842,8 @@ class Eval {
} }
} }
} }
//where:
enum Result {SUCESS, FAILURE, CONTINUE}
/** /**
* If there are classes to load, loads by calling the execution engine. * If there are classes to load, loads by calling the execution engine.
@ -893,6 +907,8 @@ class Eval {
final boolean fatal; final boolean fatal;
final String message; final String message;
long start;
long end;
ModifierDiagnostic(List<Modifier> list, boolean fatal) { ModifierDiagnostic(List<Modifier> list, boolean fatal) {
this.fatal = fatal; this.fatal = fatal;
@ -910,6 +926,8 @@ class Eval {
? "jshell.diag.modifier.single.fatal" ? "jshell.diag.modifier.single.fatal"
: "jshell.diag.modifier.single.ignore"; : "jshell.diag.modifier.single.ignore";
this.message = state.messageFormat(key, sb.toString()); this.message = state.messageFormat(key, sb.toString());
start = dis.getStartPosition(modtree);
end = dis.getEndPosition(modtree);
} }
@Override @Override
@ -919,17 +937,17 @@ class Eval {
@Override @Override
public long getPosition() { public long getPosition() {
return dis.getStartPosition(modtree); return start;
} }
@Override @Override
public long getStartPosition() { public long getStartPosition() {
return dis.getStartPosition(modtree); return start;
} }
@Override @Override
public long getEndPosition() { public long getEndPosition() {
return dis.getEndPosition(modtree); return end;
} }
@Override @Override

View file

@ -163,14 +163,15 @@ class ExpressionToTypeInfo {
if (code == null || code.isEmpty()) { if (code == null || code.isEmpty()) {
return null; return null;
} }
OuterWrap codeWrap = state.outerMap.wrapInTrialClass(Wrap.methodReturnWrap(code));
try { try {
OuterWrap codeWrap = state.outerMap.wrapInTrialClass(Wrap.methodReturnWrap(code)); return state.taskFactory.analyze(codeWrap, at -> {
AnalyzeTask at = state.taskFactory.new AnalyzeTask(codeWrap); CompilationUnitTree cu = at.firstCuTree();
CompilationUnitTree cu = at.firstCuTree(); if (at.hasErrors() || cu == null) {
if (at.hasErrors() || cu == null) { return null;
return null; }
} return new ExpressionToTypeInfo(at, cu, state).typeOfExpression();
return new ExpressionToTypeInfo(at, cu, state).typeOfExpression(); });
} catch (Exception ex) { } catch (Exception ex) {
return null; return null;
} }
@ -189,12 +190,13 @@ class ExpressionToTypeInfo {
} }
try { try {
OuterWrap codeWrap = state.outerMap.wrapInTrialClass(Wrap.methodWrap("var $$$ = " + code)); OuterWrap codeWrap = state.outerMap.wrapInTrialClass(Wrap.methodWrap("var $$$ = " + code));
AnalyzeTask at = state.taskFactory.new AnalyzeTask(codeWrap); return state.taskFactory.analyze(codeWrap, at -> {
CompilationUnitTree cu = at.firstCuTree(); CompilationUnitTree cu = at.firstCuTree();
if (at.hasErrors() || cu == null) { if (at.hasErrors() || cu == null) {
return null; return null;
} }
return new ExpressionToTypeInfo(at, cu, state).typeOfExpression(); return new ExpressionToTypeInfo(at, cu, state).typeOfExpression();
});
} catch (Exception ex) { } catch (Exception ex) {
return null; return null;
} }

View file

@ -40,8 +40,12 @@ class ReplParserFactory extends ParserFactory {
private final boolean forceExpression; private final boolean forceExpression;
public static void preRegister(Context context, boolean forceExpression) { public static void preRegister(Context context, boolean forceExpression) {
context.put(parserFactoryKey, (Context.Factory<ParserFactory>) class Mark {}
(c -> new ReplParserFactory(c, forceExpression))); if (context.get(Mark.class) == null) { //don't register the factory if Context is reused
context.put(parserFactoryKey, (Context.Factory<ParserFactory>)
(c -> new ReplParserFactory(c, forceExpression)));
context.put(Mark.class, new Mark());
}
} }
private final ScannerFactory scannerFactory; private final ScannerFactory scannerFactory;

View file

@ -236,14 +236,15 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
} }
private Tree.Kind guessKind(String code) { private Tree.Kind guessKind(String code) {
ParseTask pt = proc.taskFactory.parse(code); return proc.taskFactory.parse(code, pt -> {
List<? extends Tree> units = pt.units(); List<? extends Tree> units = pt.units();
if (units.isEmpty()) { if (units.isEmpty()) {
return Tree.Kind.BLOCK; return Tree.Kind.BLOCK;
} }
Tree unitTree = units.get(0); Tree unitTree = units.get(0);
proc.debug(DBG_COMPA, "Kind: %s -- %s\n", unitTree.getKind(), unitTree); proc.debug(DBG_COMPA, "Kind: %s -- %s\n", unitTree.getKind(), unitTree);
return unitTree.getKind(); return unitTree.getKind();
});
} }
//TODO: would be better handled through a lexer: //TODO: would be better handled through a lexer:
@ -295,182 +296,183 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
} }
private List<Suggestion> computeSuggestions(OuterWrap code, int cursor, int[] anchor) { private List<Suggestion> computeSuggestions(OuterWrap code, int cursor, int[] anchor) {
AnalyzeTask at = proc.taskFactory.new AnalyzeTask(code); return proc.taskFactory.analyze(code, at -> {
SourcePositions sp = at.trees().getSourcePositions(); SourcePositions sp = at.trees().getSourcePositions();
CompilationUnitTree topLevel = at.firstCuTree(); CompilationUnitTree topLevel = at.firstCuTree();
List<Suggestion> result = new ArrayList<>(); List<Suggestion> result = new ArrayList<>();
TreePath tp = pathFor(topLevel, sp, code.snippetIndexToWrapIndex(cursor)); TreePath tp = pathFor(topLevel, sp, code.snippetIndexToWrapIndex(cursor));
if (tp != null) { if (tp != null) {
Scope scope = at.trees().getScope(tp); Scope scope = at.trees().getScope(tp);
Predicate<Element> accessibility = createAccessibilityFilter(at, tp); Predicate<Element> accessibility = createAccessibilityFilter(at, tp);
Predicate<Element> smartTypeFilter; Predicate<Element> smartTypeFilter;
Predicate<Element> smartFilter; Predicate<Element> smartFilter;
Iterable<TypeMirror> targetTypes = findTargetType(at, tp); Iterable<TypeMirror> targetTypes = findTargetType(at, tp);
if (targetTypes != null) { if (targetTypes != null) {
smartTypeFilter = el -> { smartTypeFilter = el -> {
TypeMirror resultOf = resultTypeOf(el); TypeMirror resultOf = resultTypeOf(el);
return Util.stream(targetTypes) return Util.stream(targetTypes)
.anyMatch(targetType -> at.getTypes().isAssignable(resultOf, targetType)); .anyMatch(targetType -> at.getTypes().isAssignable(resultOf, targetType));
}; };
smartFilter = IS_CLASS.negate() smartFilter = IS_CLASS.negate()
.and(IS_INTERFACE.negate()) .and(IS_INTERFACE.negate())
.and(IS_PACKAGE.negate()) .and(IS_PACKAGE.negate())
.and(smartTypeFilter); .and(smartTypeFilter);
} else { } else {
smartFilter = TRUE; smartFilter = TRUE;
smartTypeFilter = TRUE; smartTypeFilter = TRUE;
} }
switch (tp.getLeaf().getKind()) { switch (tp.getLeaf().getKind()) {
case MEMBER_SELECT: { case MEMBER_SELECT: {
MemberSelectTree mst = (MemberSelectTree)tp.getLeaf(); MemberSelectTree mst = (MemberSelectTree)tp.getLeaf();
if (mst.getIdentifier().contentEquals("*")) if (mst.getIdentifier().contentEquals("*"))
break; break;
TreePath exprPath = new TreePath(tp, mst.getExpression()); TreePath exprPath = new TreePath(tp, mst.getExpression());
TypeMirror site = at.trees().getTypeMirror(exprPath); TypeMirror site = at.trees().getTypeMirror(exprPath);
boolean staticOnly = isStaticContext(at, exprPath); boolean staticOnly = isStaticContext(at, exprPath);
ImportTree it = findImport(tp); ImportTree it = findImport(tp);
boolean isImport = it != null; boolean isImport = it != null;
List<? extends Element> members = membersOf(at, site, staticOnly && !isImport); List<? extends Element> members = membersOf(at, site, staticOnly && !isImport);
Predicate<Element> filter = accessibility; Predicate<Element> filter = accessibility;
Function<Boolean, String> paren = DEFAULT_PAREN; Function<Boolean, String> paren = DEFAULT_PAREN;
if (isNewClass(tp)) { // new xxx.| if (isNewClass(tp)) { // new xxx.|
Predicate<Element> constructorFilter = accessibility.and(IS_CONSTRUCTOR) Predicate<Element> constructorFilter = accessibility.and(IS_CONSTRUCTOR)
.and(el -> { .and(el -> {
if (el.getEnclosingElement().getEnclosingElement().getKind() == ElementKind.CLASS) { if (el.getEnclosingElement().getEnclosingElement().getKind() == ElementKind.CLASS) {
return el.getEnclosingElement().getModifiers().contains(Modifier.STATIC); return el.getEnclosingElement().getModifiers().contains(Modifier.STATIC);
} }
return true; return true;
}); });
addElements(membersOf(at, members), constructorFilter, smartFilter, result); addElements(membersOf(at, members), constructorFilter, smartFilter, result);
filter = filter.and(IS_PACKAGE); filter = filter.and(IS_PACKAGE);
} else if (isThrowsClause(tp)) { } else if (isThrowsClause(tp)) {
staticOnly = true; staticOnly = true;
filter = filter.and(IS_PACKAGE.or(IS_CLASS).or(IS_INTERFACE));
smartFilter = IS_PACKAGE.negate().and(smartTypeFilter);
} else if (isImport) {
paren = NO_PAREN;
if (!it.isStatic()) {
filter = filter.and(IS_PACKAGE.or(IS_CLASS).or(IS_INTERFACE)); filter = filter.and(IS_PACKAGE.or(IS_CLASS).or(IS_INTERFACE));
} smartFilter = IS_PACKAGE.negate().and(smartTypeFilter);
} else { } else if (isImport) {
filter = filter.and(IS_CONSTRUCTOR.negate()); paren = NO_PAREN;
} if (!it.isStatic()) {
filter = filter.and(IS_PACKAGE.or(IS_CLASS).or(IS_INTERFACE));
filter = filter.and(staticOnly ? STATIC_ONLY : INSTANCE_ONLY); }
addElements(members, filter, smartFilter, paren, result);
break;
}
case IDENTIFIER:
if (isNewClass(tp)) {
Function<Element, Iterable<? extends Element>> listEnclosed =
el -> el.getKind() == ElementKind.PACKAGE ? Collections.singletonList(el)
: el.getEnclosedElements();
Predicate<Element> filter = accessibility.and(IS_CONSTRUCTOR.or(IS_PACKAGE));
NewClassTree newClassTree = (NewClassTree)tp.getParentPath().getLeaf();
ExpressionTree enclosingExpression = newClassTree.getEnclosingExpression();
if (enclosingExpression != null) { // expr.new IDENT|
TypeMirror site = at.trees().getTypeMirror(new TreePath(tp, enclosingExpression));
filter = filter.and(el -> el.getEnclosingElement().getKind() == ElementKind.CLASS && !el.getEnclosingElement().getModifiers().contains(Modifier.STATIC));
addElements(membersOf(at, membersOf(at, site, false)), filter, smartFilter, result);
} else { } else {
addScopeElements(at, scope, listEnclosed, filter, smartFilter, result); filter = filter.and(IS_CONSTRUCTOR.negate());
} }
filter = filter.and(staticOnly ? STATIC_ONLY : INSTANCE_ONLY);
addElements(members, filter, smartFilter, paren, result);
break; break;
} }
if (isThrowsClause(tp)) { case IDENTIFIER:
Predicate<Element> accept = accessibility.and(STATIC_ONLY) if (isNewClass(tp)) {
.and(IS_PACKAGE.or(IS_CLASS).or(IS_INTERFACE)); Function<Element, Iterable<? extends Element>> listEnclosed =
addScopeElements(at, scope, IDENTITY, accept, IS_PACKAGE.negate().and(smartTypeFilter), result); el -> el.getKind() == ElementKind.PACKAGE ? Collections.singletonList(el)
: el.getEnclosedElements();
Predicate<Element> filter = accessibility.and(IS_CONSTRUCTOR.or(IS_PACKAGE));
NewClassTree newClassTree = (NewClassTree)tp.getParentPath().getLeaf();
ExpressionTree enclosingExpression = newClassTree.getEnclosingExpression();
if (enclosingExpression != null) { // expr.new IDENT|
TypeMirror site = at.trees().getTypeMirror(new TreePath(tp, enclosingExpression));
filter = filter.and(el -> el.getEnclosingElement().getKind() == ElementKind.CLASS && !el.getEnclosingElement().getModifiers().contains(Modifier.STATIC));
addElements(membersOf(at, membersOf(at, site, false)), filter, smartFilter, result);
} else {
addScopeElements(at, scope, listEnclosed, filter, smartFilter, result);
}
break;
}
if (isThrowsClause(tp)) {
Predicate<Element> accept = accessibility.and(STATIC_ONLY)
.and(IS_PACKAGE.or(IS_CLASS).or(IS_INTERFACE));
addScopeElements(at, scope, IDENTITY, accept, IS_PACKAGE.negate().and(smartTypeFilter), result);
break;
}
ImportTree it = findImport(tp);
if (it != null) {
// the context of the identifier is an import, look for
// package names that start with the identifier.
// If and when Java allows imports from the default
// package to the the default package which would allow
// JShell to change to use the default package, and that
// change is done, then this should use some variation
// of membersOf(at, at.getElements().getPackageElement("").asType(), false)
addElements(listPackages(at, ""),
it.isStatic()
? STATIC_ONLY.and(accessibility)
: accessibility,
smartFilter, result);
}
break;
case CLASS: {
Predicate<Element> accept = accessibility.and(IS_TYPE);
addScopeElements(at, scope, IDENTITY, accept, smartFilter, result);
addElements(primitivesOrVoid(at), TRUE, smartFilter, result);
break; break;
} }
ImportTree it = findImport(tp); case BLOCK:
if (it != null) { case EMPTY_STATEMENT:
// the context of the identifier is an import, look for case ERRONEOUS: {
// package names that start with the identifier. boolean staticOnly = ReplResolve.isStatic(((JavacScope)scope).getEnv());
// If and when Java allows imports from the default Predicate<Element> accept = accessibility.and(staticOnly ? STATIC_ONLY : TRUE);
// package to the the default package which would allow if (isClass(tp)) {
// JShell to change to use the default package, and that ClassTree clazz = (ClassTree) tp.getParentPath().getLeaf();
// change is done, then this should use some variation if (clazz.getExtendsClause() == tp.getLeaf()) {
// of membersOf(at, at.getElements().getPackageElement("").asType(), false) accept = accept.and(IS_TYPE);
addElements(listPackages(at, ""), smartFilter = smartFilter.and(el -> el.getKind() == ElementKind.CLASS);
it.isStatic() } else {
? STATIC_ONLY.and(accessibility) Predicate<Element> f = smartFilterFromList(at, tp, clazz.getImplementsClause(), tp.getLeaf());
: accessibility, if (f != null) {
smartFilter, result); accept = accept.and(IS_TYPE);
} smartFilter = f.and(el -> el.getKind() == ElementKind.INTERFACE);
break; }
case CLASS: { }
Predicate<Element> accept = accessibility.and(IS_TYPE); } else if (isTypeParameter(tp)) {
addScopeElements(at, scope, IDENTITY, accept, smartFilter, result); TypeParameterTree tpt = (TypeParameterTree) tp.getParentPath().getLeaf();
addElements(primitivesOrVoid(at), TRUE, smartFilter, result); Predicate<Element> f = smartFilterFromList(at, tp, tpt.getBounds(), tp.getLeaf());
break;
}
case BLOCK:
case EMPTY_STATEMENT:
case ERRONEOUS: {
boolean staticOnly = ReplResolve.isStatic(((JavacScope)scope).getEnv());
Predicate<Element> accept = accessibility.and(staticOnly ? STATIC_ONLY : TRUE);
if (isClass(tp)) {
ClassTree clazz = (ClassTree) tp.getParentPath().getLeaf();
if (clazz.getExtendsClause() == tp.getLeaf()) {
accept = accept.and(IS_TYPE);
smartFilter = smartFilter.and(el -> el.getKind() == ElementKind.CLASS);
} else {
Predicate<Element> f = smartFilterFromList(at, tp, clazz.getImplementsClause(), tp.getLeaf());
if (f != null) { if (f != null) {
accept = accept.and(IS_TYPE); accept = accept.and(IS_TYPE);
smartFilter = f.and(el -> el.getKind() == ElementKind.INTERFACE); smartFilter = f;
if (!tpt.getBounds().isEmpty() && tpt.getBounds().get(0) != tp.getLeaf()) {
smartFilter = smartFilter.and(el -> el.getKind() == ElementKind.INTERFACE);
}
}
} else if (isVariable(tp)) {
VariableTree var = (VariableTree) tp.getParentPath().getLeaf();
if (var.getType() == tp.getLeaf()) {
accept = accept.and(IS_TYPE);
} }
} }
} else if (isTypeParameter(tp)) {
TypeParameterTree tpt = (TypeParameterTree) tp.getParentPath().getLeaf();
Predicate<Element> f = smartFilterFromList(at, tp, tpt.getBounds(), tp.getLeaf());
if (f != null) {
accept = accept.and(IS_TYPE);
smartFilter = f;
if (!tpt.getBounds().isEmpty() && tpt.getBounds().get(0) != tp.getLeaf()) {
smartFilter = smartFilter.and(el -> el.getKind() == ElementKind.INTERFACE);
}
}
} else if (isVariable(tp)) {
VariableTree var = (VariableTree) tp.getParentPath().getLeaf();
if (var.getType() == tp.getLeaf()) {
accept = accept.and(IS_TYPE);
}
}
addScopeElements(at, scope, IDENTITY, accept, smartFilter, result); addScopeElements(at, scope, IDENTITY, accept, smartFilter, result);
Tree parent = tp.getParentPath().getLeaf(); Tree parent = tp.getParentPath().getLeaf();
switch (parent.getKind()) { switch (parent.getKind()) {
case VARIABLE: case VARIABLE:
accept = ((VariableTree)parent).getType() == tp.getLeaf() ? accept = ((VariableTree)parent).getType() == tp.getLeaf() ?
IS_VOID.negate() : IS_VOID.negate() :
TRUE; TRUE;
break; break;
case PARAMETERIZED_TYPE: // TODO: JEP 218: Generics over Primitive Types case PARAMETERIZED_TYPE: // TODO: JEP 218: Generics over Primitive Types
case TYPE_PARAMETER: case TYPE_PARAMETER:
case CLASS: case CLASS:
case INTERFACE: case INTERFACE:
case ENUM: case ENUM:
accept = FALSE; accept = FALSE;
break; break;
default: default:
accept = TRUE; accept = TRUE;
break; break;
}
addElements(primitivesOrVoid(at), accept, smartFilter, result);
break;
} }
addElements(primitivesOrVoid(at), accept, smartFilter, result);
break;
} }
} }
} anchor[0] = cursor;
anchor[0] = cursor; return result;
return result; });
} }
private static final Set<Kind> CLASS_KINDS = EnumSet.of( private static final Set<Kind> CLASS_KINDS = EnumSet.of(
@ -1167,78 +1169,79 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
return Collections.emptyList(); return Collections.emptyList();
OuterWrap codeWrap = proc.outerMap.wrapInTrialClass(Wrap.methodWrap(code)); OuterWrap codeWrap = proc.outerMap.wrapInTrialClass(Wrap.methodWrap(code));
AnalyzeTask at = proc.taskFactory.new AnalyzeTask(codeWrap, keepParameterNames); return proc.taskFactory.analyze(codeWrap, List.of(keepParameterNames), at -> {
SourcePositions sp = at.trees().getSourcePositions(); SourcePositions sp = at.trees().getSourcePositions();
CompilationUnitTree topLevel = at.firstCuTree(); CompilationUnitTree topLevel = at.firstCuTree();
TreePath tp = pathFor(topLevel, sp, codeWrap.snippetIndexToWrapIndex(cursor)); TreePath tp = pathFor(topLevel, sp, codeWrap.snippetIndexToWrapIndex(cursor));
if (tp == null) if (tp == null)
return Collections.emptyList(); return Collections.emptyList();
TreePath prevPath = null; TreePath prevPath = null;
while (tp != null && tp.getLeaf().getKind() != Kind.METHOD_INVOCATION && while (tp != null && tp.getLeaf().getKind() != Kind.METHOD_INVOCATION &&
tp.getLeaf().getKind() != Kind.NEW_CLASS && tp.getLeaf().getKind() != Kind.IDENTIFIER && tp.getLeaf().getKind() != Kind.NEW_CLASS && tp.getLeaf().getKind() != Kind.IDENTIFIER &&
tp.getLeaf().getKind() != Kind.MEMBER_SELECT) { tp.getLeaf().getKind() != Kind.MEMBER_SELECT) {
prevPath = tp; prevPath = tp;
tp = tp.getParentPath(); tp = tp.getParentPath();
} }
if (tp == null) if (tp == null)
return Collections.emptyList(); return Collections.emptyList();
Stream<Element> elements; Stream<Element> elements;
Iterable<Pair<ExecutableElement, ExecutableType>> candidates; Iterable<Pair<ExecutableElement, ExecutableType>> candidates;
List<? extends ExpressionTree> arguments; List<? extends ExpressionTree> arguments;
if (tp.getLeaf().getKind() == Kind.METHOD_INVOCATION || tp.getLeaf().getKind() == Kind.NEW_CLASS) { if (tp.getLeaf().getKind() == Kind.METHOD_INVOCATION || tp.getLeaf().getKind() == Kind.NEW_CLASS) {
if (tp.getLeaf().getKind() == Kind.METHOD_INVOCATION) { if (tp.getLeaf().getKind() == Kind.METHOD_INVOCATION) {
MethodInvocationTree mit = (MethodInvocationTree) tp.getLeaf(); MethodInvocationTree mit = (MethodInvocationTree) tp.getLeaf();
candidates = methodCandidates(at, tp); candidates = methodCandidates(at, tp);
arguments = mit.getArguments(); arguments = mit.getArguments();
} else {
NewClassTree nct = (NewClassTree) tp.getLeaf();
candidates = newClassCandidates(at, tp);
arguments = nct.getArguments();
}
if (!isEmptyArgumentsContext(arguments)) {
List<TypeMirror> actuals = computeActualInvocationTypes(at, arguments, prevPath);
List<TypeMirror> fullActuals = actuals != null ? actuals : Collections.emptyList();
candidates =
this.filterExecutableTypesByArguments(at, candidates, fullActuals)
.stream()
.filter(method -> parameterType(method.fst, method.snd, fullActuals.size(), true).findAny().isPresent())
.collect(Collectors.toList());
}
elements = Util.stream(candidates).map(method -> method.fst);
} else if (tp.getLeaf().getKind() == Kind.IDENTIFIER || tp.getLeaf().getKind() == Kind.MEMBER_SELECT) {
Element el = at.trees().getElement(tp);
if (el == null ||
el.asType().getKind() == TypeKind.ERROR ||
(el.getKind() == ElementKind.PACKAGE && el.getEnclosedElements().isEmpty())) {
//erroneous element:
return Collections.emptyList();
}
elements = Stream.of(el);
} else { } else {
NewClassTree nct = (NewClassTree) tp.getLeaf();
candidates = newClassCandidates(at, tp);
arguments = nct.getArguments();
}
if (!isEmptyArgumentsContext(arguments)) {
List<TypeMirror> actuals = computeActualInvocationTypes(at, arguments, prevPath);
List<TypeMirror> fullActuals = actuals != null ? actuals : Collections.emptyList();
candidates =
this.filterExecutableTypesByArguments(at, candidates, fullActuals)
.stream()
.filter(method -> parameterType(method.fst, method.snd, fullActuals.size(), true).findAny().isPresent())
.collect(Collectors.toList());
}
elements = Util.stream(candidates).map(method -> method.fst);
} else if (tp.getLeaf().getKind() == Kind.IDENTIFIER || tp.getLeaf().getKind() == Kind.MEMBER_SELECT) {
Element el = at.trees().getElement(tp);
if (el == null ||
el.asType().getKind() == TypeKind.ERROR ||
(el.getKind() == ElementKind.PACKAGE && el.getEnclosedElements().isEmpty())) {
//erroneous element:
return Collections.emptyList(); return Collections.emptyList();
} }
elements = Stream.of(el); List<Documentation> result = Collections.emptyList();
} else {
return Collections.emptyList();
}
List<Documentation> result = Collections.emptyList(); try (JavadocHelper helper = JavadocHelper.create(at.task, findSources())) {
result = elements.map(el -> constructDocumentation(at, helper, el, computeJavadoc))
.filter(Objects::nonNull)
.collect(Collectors.toList());
} catch (IOException ex) {
proc.debug(ex, "JavadocHelper.close()");
}
try (JavadocHelper helper = JavadocHelper.create(at.task, findSources())) { return result;
result = elements.map(el -> constructDocumentation(at, helper, el, computeJavadoc)) });
.filter(Objects::nonNull)
.collect(Collectors.toList());
} catch (IOException ex) {
proc.debug(ex, "JavadocHelper.close()");
}
return result;
} }
private Documentation constructDocumentation(AnalyzeTask at, JavadocHelper helper, Element el, boolean computeJavadoc) { private Documentation constructDocumentation(AnalyzeTask at, JavadocHelper helper, Element el, boolean computeJavadoc) {
@ -1494,51 +1497,52 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
@Override @Override
public QualifiedNames listQualifiedNames(String code, int cursor) { public QualifiedNames listQualifiedNames(String code, int cursor) {
code = code.substring(0, cursor); String codeFin = code.substring(0, cursor);
if (code.trim().isEmpty()) { if (codeFin.trim().isEmpty()) {
return new QualifiedNames(Collections.emptyList(), -1, true, false); return new QualifiedNames(Collections.emptyList(), -1, true, false);
} }
OuterWrap codeWrap; OuterWrap codeWrap;
switch (guessKind(code)) { switch (guessKind(codeFin)) {
case IMPORT: case IMPORT:
return new QualifiedNames(Collections.emptyList(), -1, true, false); return new QualifiedNames(Collections.emptyList(), -1, true, false);
case METHOD: case METHOD:
codeWrap = proc.outerMap.wrapInTrialClass(Wrap.classMemberWrap(code)); codeWrap = proc.outerMap.wrapInTrialClass(Wrap.classMemberWrap(codeFin));
break; break;
default: default:
codeWrap = proc.outerMap.wrapInTrialClass(Wrap.methodWrap(code)); codeWrap = proc.outerMap.wrapInTrialClass(Wrap.methodWrap(codeFin));
break; break;
} }
AnalyzeTask at = proc.taskFactory.new AnalyzeTask(codeWrap); return proc.taskFactory.analyze(codeWrap, at -> {
SourcePositions sp = at.trees().getSourcePositions(); SourcePositions sp = at.trees().getSourcePositions();
CompilationUnitTree topLevel = at.firstCuTree(); CompilationUnitTree topLevel = at.firstCuTree();
TreePath tp = pathFor(topLevel, sp, codeWrap.snippetIndexToWrapIndex(code.length())); TreePath tp = pathFor(topLevel, sp, codeWrap.snippetIndexToWrapIndex(codeFin.length()));
if (tp.getLeaf().getKind() != Kind.IDENTIFIER) { if (tp.getLeaf().getKind() != Kind.IDENTIFIER) {
return new QualifiedNames(Collections.emptyList(), -1, true, false); return new QualifiedNames(Collections.emptyList(), -1, true, false);
} }
Scope scope = at.trees().getScope(tp); Scope scope = at.trees().getScope(tp);
TypeMirror type = at.trees().getTypeMirror(tp); TypeMirror type = at.trees().getTypeMirror(tp);
Element el = at.trees().getElement(tp); Element el = at.trees().getElement(tp);
boolean erroneous = (type.getKind() == TypeKind.ERROR && el.getKind() == ElementKind.CLASS) || boolean erroneous = (type.getKind() == TypeKind.ERROR && el.getKind() == ElementKind.CLASS) ||
(el.getKind() == ElementKind.PACKAGE && el.getEnclosedElements().isEmpty()); (el.getKind() == ElementKind.PACKAGE && el.getEnclosedElements().isEmpty());
String simpleName = ((IdentifierTree) tp.getLeaf()).getName().toString(); String simpleName = ((IdentifierTree) tp.getLeaf()).getName().toString();
boolean upToDate; boolean upToDate;
List<String> result; List<String> result;
synchronized (currentIndexes) { synchronized (currentIndexes) {
upToDate = classpathVersion == indexVersion; upToDate = classpathVersion == indexVersion;
result = currentIndexes.values() result = currentIndexes.values()
.stream() .stream()
.flatMap(idx -> idx.classSimpleName2FQN.getOrDefault(simpleName, .flatMap(idx -> idx.classSimpleName2FQN.getOrDefault(simpleName,
Collections.emptyList()).stream()) Collections.emptyList()).stream())
.distinct() .distinct()
.filter(fqn -> isAccessible(at, scope, fqn)) .filter(fqn -> isAccessible(at, scope, fqn))
.sorted() .sorted()
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
return new QualifiedNames(result, simpleName.length(), upToDate, !erroneous); return new QualifiedNames(result, simpleName.length(), upToDate, !erroneous);
});
} }
private boolean isAccessible(AnalyzeTask at, Scope scope, String fqn) { private boolean isAccessible(AnalyzeTask at, Scope scope, String fqn) {

View file

@ -29,10 +29,8 @@ import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.Tree; import com.sun.source.tree.Tree;
import com.sun.source.util.Trees; import com.sun.source.util.Trees;
import com.sun.tools.javac.api.JavacTaskImpl; import com.sun.tools.javac.api.JavacTaskImpl;
import com.sun.tools.javac.api.JavacTool;
import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Context;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import javax.tools.Diagnostic; import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector; import javax.tools.DiagnosticCollector;
@ -62,18 +60,28 @@ import javax.tools.FileObject;
import jdk.jshell.MemoryFileManager.SourceMemoryJavaFileObject; import jdk.jshell.MemoryFileManager.SourceMemoryJavaFileObject;
import java.lang.Runtime.Version; import java.lang.Runtime.Version;
import java.nio.CharBuffer; import java.nio.CharBuffer;
import java.util.function.BiFunction;
import com.sun.source.tree.Tree.Kind; import com.sun.source.tree.Tree.Kind;
import com.sun.source.util.TaskEvent;
import com.sun.source.util.TaskListener;
import com.sun.tools.javac.api.JavacTaskPool;
import com.sun.tools.javac.code.ClassFinder;
import com.sun.tools.javac.code.Kinds; import com.sun.tools.javac.code.Kinds;
import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Symbol.PackageSymbol;
import com.sun.tools.javac.code.Symbol.VarSymbol; import com.sun.tools.javac.code.Symbol.VarSymbol;
import com.sun.tools.javac.code.Symtab;
import com.sun.tools.javac.comp.Attr;
import com.sun.tools.javac.parser.Parser; import com.sun.tools.javac.parser.Parser;
import com.sun.tools.javac.parser.ParserFactory;
import com.sun.tools.javac.tree.JCTree.JCClassDecl; import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.tree.JCTree.JCExpression;
import com.sun.tools.javac.tree.JCTree.JCTypeCast; import com.sun.tools.javac.tree.JCTree.JCTypeCast;
import com.sun.tools.javac.tree.JCTree.Tag; import com.sun.tools.javac.tree.JCTree.Tag;
import com.sun.tools.javac.util.Context.Factory; import com.sun.tools.javac.util.Log;
import com.sun.tools.javac.util.Log.DiscardDiagnosticHandler; import com.sun.tools.javac.util.Log.DiscardDiagnosticHandler;
import com.sun.tools.javac.util.Names;
import jdk.jshell.Snippet.Status; import jdk.jshell.Snippet.Status;
/** /**
@ -101,6 +109,7 @@ class TaskFactory {
} }
this.fileManager = new MemoryFileManager( this.fileManager = new MemoryFileManager(
compiler.getStandardFileManager(null, null, null), state); compiler.getStandardFileManager(null, null, null), state);
initTaskPool();
} }
void addToClasspath(String path) { void addToClasspath(String path) {
@ -108,27 +117,130 @@ class TaskFactory {
List<String> args = new ArrayList<>(); List<String> args = new ArrayList<>();
args.add(classpath); args.add(classpath);
fileManager().handleOption("-classpath", args.iterator()); fileManager().handleOption("-classpath", args.iterator());
initTaskPool();
} }
MemoryFileManager fileManager() { MemoryFileManager fileManager() {
return fileManager; return fileManager;
} }
public <Z> Z parse(String source,
boolean forceExpression,
Worker<ParseTask, Z> worker) {
StringSourceHandler sh = new StringSourceHandler();
return runTask(Stream.of(source),
sh,
List.of("-XDallowStringFolding=false", "-proc:none",
"-XDneedsReplParserFactory=" + forceExpression),
(jti, diagnostics) -> new ParseTask(sh, jti, diagnostics, forceExpression),
worker);
}
public <Z> Z analyze(OuterWrap wrap,
Worker<AnalyzeTask, Z> worker) {
return analyze(Collections.singletonList(wrap), worker);
}
public <Z> Z analyze(OuterWrap wrap,
List<String> extraArgs,
Worker<AnalyzeTask, Z> worker) {
return analyze(Collections.singletonList(wrap), extraArgs, worker);
}
public <Z> Z analyze(Collection<OuterWrap> wraps,
Worker<AnalyzeTask, Z> worker) {
return analyze(wraps, Collections.emptyList(), worker);
}
public <Z> Z analyze(Collection<OuterWrap> wraps,
List<String> extraArgs,
Worker<AnalyzeTask, Z> worker) {
WrapSourceHandler sh = new WrapSourceHandler();
List<String> allOptions = new ArrayList<>();
allOptions.add("--should-stop:at=FLOW");
allOptions.add("-Xlint:unchecked");
allOptions.add("-proc:none");
allOptions.addAll(extraArgs);
return runTask(wraps.stream(),
sh,
allOptions,
(jti, diagnostics) -> new AnalyzeTask(sh, jti, diagnostics),
worker);
}
public <Z> Z compile(Collection<OuterWrap> wraps,
Worker<CompileTask, Z> worker) {
WrapSourceHandler sh = new WrapSourceHandler();
return runTask(wraps.stream(),
sh,
List.of("-Xlint:unchecked", "-proc:none", "-parameters"),
(jti, diagnostics) -> new CompileTask(sh, jti, diagnostics),
worker);
}
private <S, T extends BaseTask, Z> Z runTask(Stream<S> inputs,
SourceHandler<S> sh,
List<String> options,
BiFunction<JavacTaskImpl, DiagnosticCollector<JavaFileObject>, T> creator,
Worker<T, Z> worker) {
List<String> allOptions = new ArrayList<>(options.size() + state.extraCompilerOptions.size());
allOptions.addAll(options);
allOptions.addAll(state.extraCompilerOptions);
Iterable<? extends JavaFileObject> compilationUnits = inputs
.map(in -> sh.sourceToFileObject(fileManager, in))
.collect(Collectors.toList());
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
return javacTaskPool.getTask(null, fileManager, diagnostics, allOptions, null,
compilationUnits, task -> {
JavacTaskImpl jti = (JavacTaskImpl) task;
Context context = jti.getContext();
jti.addTaskListener(new TaskListenerImpl(context, state));
try {
return worker.withTask(creator.apply(jti, diagnostics));
} finally {
//additional cleanup: purge the REPL package:
Symtab syms = Symtab.instance(context);
Names names = Names.instance(context);
PackageSymbol repl = syms.getPackage(syms.unnamedModule, names.fromString(Util.REPL_PACKAGE));
if (repl != null) {
for (ClassSymbol clazz : syms.getAllClasses()) {
if (clazz.packge() == repl) {
syms.removeClass(syms.unnamedModule, clazz.flatName());
}
}
repl.members_field = null;
repl.completer = ClassFinder.instance(context).getCompleter();
}
}
});
}
interface Worker<T extends BaseTask, Z> {
public Z withTask(T task);
}
// Parse a snippet and return our parse task handler // Parse a snippet and return our parse task handler
ParseTask parse(final String source) { <Z> Z parse(final String source, Worker<ParseTask, Z> worker) {
ParseTask pt = state.taskFactory.new ParseTask(source, false); return parse(source, false, pt -> {
if (!pt.units().isEmpty() if (!pt.units().isEmpty()
&& pt.units().get(0).getKind() == Kind.EXPRESSION_STATEMENT && pt.units().get(0).getKind() == Kind.EXPRESSION_STATEMENT
&& pt.getDiagnostics().hasOtherThanNotStatementErrors()) { && pt.getDiagnostics().hasOtherThanNotStatementErrors()) {
// It failed, it may be an expression being incorrectly // It failed, it may be an expression being incorrectly
// parsed as having a leading type variable, example: a < b // parsed as having a leading type variable, example: a < b
// Try forcing interpretation as an expression // Try forcing interpretation as an expression
ParseTask ept = state.taskFactory.new ParseTask(source, true); return parse(source, true, ept -> {
if (!ept.getDiagnostics().hasOtherThanNotStatementErrors()) { if (!ept.getDiagnostics().hasOtherThanNotStatementErrors()) {
return ept; return worker.withTask(ept);
} else {
return worker.withTask(pt);
}
});
} }
} return worker.withTask(pt);
return pt; });
} }
private interface SourceHandler<T> { private interface SourceHandler<T> {
@ -210,11 +322,12 @@ class TaskFactory {
private final Iterable<? extends CompilationUnitTree> cuts; private final Iterable<? extends CompilationUnitTree> cuts;
private final List<? extends Tree> units; private final List<? extends Tree> units;
ParseTask(final String source, final boolean forceExpression) { private ParseTask(SourceHandler<String> sh,
super(Stream.of(source), JavacTaskImpl task,
new StringSourceHandler(), DiagnosticCollector<JavaFileObject> diagnostics,
"-XDallowStringFolding=false", "-proc:none"); boolean forceExpression) {
ReplParserFactory.preRegister(getContext(), forceExpression); super(sh, task, diagnostics);
ReplParserFactory.preRegister(context, forceExpression);
cuts = parse(); cuts = parse();
units = Util.stream(cuts) units = Util.stream(cuts)
.flatMap(cut -> { .flatMap(cut -> {
@ -249,22 +362,10 @@ class TaskFactory {
private final Iterable<? extends CompilationUnitTree> cuts; private final Iterable<? extends CompilationUnitTree> cuts;
AnalyzeTask(final OuterWrap wrap, String... extraArgs) { private AnalyzeTask(SourceHandler<OuterWrap> sh,
this(Collections.singletonList(wrap), extraArgs); JavacTaskImpl task,
} DiagnosticCollector<JavaFileObject> diagnostics) {
super(sh, task, diagnostics);
AnalyzeTask(final Collection<OuterWrap> wraps, String... extraArgs) {
this(wraps.stream(),
new WrapSourceHandler(),
Util.join(new String[] {
"--should-stop:at=FLOW", "-Xlint:unchecked",
"-proc:none"
}, extraArgs));
}
private <T>AnalyzeTask(final Stream<T> stream, SourceHandler<T> sourceHandler,
String... extraOptions) {
super(stream, sourceHandler, extraOptions);
cuts = analyze(); cuts = analyze();
} }
@ -299,9 +400,10 @@ class TaskFactory {
private final Map<OuterWrap, List<OutputMemoryJavaFileObject>> classObjs = new HashMap<>(); private final Map<OuterWrap, List<OutputMemoryJavaFileObject>> classObjs = new HashMap<>();
CompileTask(final Collection<OuterWrap> wraps) { CompileTask(SourceHandler<OuterWrap>sh,
super(wraps.stream(), new WrapSourceHandler(), JavacTaskImpl jti,
"-Xlint:unchecked", "-proc:none", "-parameters"); DiagnosticCollector<JavaFileObject> diagnostics) {
super(sh, jti, diagnostics);
} }
boolean compile() { boolean compile() {
@ -346,32 +448,30 @@ class TaskFactory {
} }
} }
private JavacTaskPool javacTaskPool;
private void initTaskPool() {
javacTaskPool = new JavacTaskPool(5);
}
abstract class BaseTask { abstract class BaseTask {
final DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>(); final DiagnosticCollector<JavaFileObject> diagnostics;
final JavacTaskImpl task; final JavacTaskImpl task;
private DiagList diags = null; private DiagList diags = null;
private final SourceHandler<?> sourceHandler; private final SourceHandler<?> sourceHandler;
final Context context = new Context(); final Context context;
private Types types; private Types types;
private JavacMessages messages; private JavacMessages messages;
private Trees trees; private Trees trees;
private <T>BaseTask(Stream<T> inputs, private <T>BaseTask(SourceHandler<T> sh,
//BiFunction<MemoryFileManager, T, JavaFileObject> sfoCreator, JavacTaskImpl task,
SourceHandler<T> sh, DiagnosticCollector<JavaFileObject> diagnostics) {
String... extraOptions) {
this.sourceHandler = sh; this.sourceHandler = sh;
List<String> options = new ArrayList<>(extraOptions.length + state.extraCompilerOptions.size()); this.task = task;
options.addAll(Arrays.asList(extraOptions)); context = task.getContext();
options.addAll(state.extraCompilerOptions); this.diagnostics = diagnostics;
Iterable<? extends JavaFileObject> compilationUnits = inputs
.map(in -> sh.sourceToFileObject(fileManager, in))
.collect(Collectors.toList());
JShellJavaCompiler.preRegister(context, state);
this.task = (JavacTaskImpl) ((JavacTool) compiler).getTask(null,
fileManager, diagnostics, options, null,
compilationUnits, context);
} }
abstract Iterable<? extends CompilationUnitTree> cuTrees(); abstract Iterable<? extends CompilationUnitTree> cuTrees();
@ -478,32 +578,36 @@ class TaskFactory {
} }
} }
private static final class JShellJavaCompiler extends com.sun.tools.javac.main.JavaCompiler { private static final class TaskListenerImpl implements TaskListener {
public static void preRegister(Context c, JShell state) {
c.put(compilerKey, (Factory<com.sun.tools.javac.main.JavaCompiler>) i -> new JShellJavaCompiler(i, state));
}
private final Context context;
private final JShell state; private final JShell state;
public JShellJavaCompiler(Context context, JShell state) { public TaskListenerImpl(Context context, JShell state) {
super(context); this.context = context;
this.state = state; this.state = state;
} }
@Override @Override
public void processAnnotations(com.sun.tools.javac.util.List<JCCompilationUnit> roots, Collection<String> classnames) { public void finished(TaskEvent e) {
super.processAnnotations(roots, classnames); if (e.getKind() != TaskEvent.Kind.ENTER)
return ;
state.maps state.maps
.snippetList() .snippetList()
.stream() .stream()
.filter(s -> s.status() == Status.VALID) .filter(s -> s.status() == Status.VALID)
.filter(s -> s.kind() == Snippet.Kind.VAR) .filter(s -> s.kind() == Snippet.Kind.VAR)
.filter(s -> s.subKind() == Snippet.SubKind.VAR_DECLARATION_WITH_INITIALIZER_SUBKIND) .filter(s -> s.subKind() == Snippet.SubKind.VAR_DECLARATION_WITH_INITIALIZER_SUBKIND)
.forEach(s -> setVariableType(roots, (VarSnippet) s)); .forEach(s -> setVariableType((JCCompilationUnit) e.getCompilationUnit(), (VarSnippet) s));
} }
private void setVariableType(com.sun.tools.javac.util.List<JCCompilationUnit> roots, VarSnippet s) { private void setVariableType(JCCompilationUnit root, VarSnippet s) {
Symtab syms = Symtab.instance(context);
Names names = Names.instance(context);
Log log = Log.instance(context);
ParserFactory parserFactory = ParserFactory.instance(context);
Attr attr = Attr.instance(context);
ClassSymbol clazz = syms.getClass(syms.unnamedModule, names.fromString(s.classFullName())); ClassSymbol clazz = syms.getClass(syms.unnamedModule, names.fromString(s.classFullName()));
if (clazz == null || !clazz.isCompleted()) if (clazz == null || !clazz.isCompleted())
return; return;
@ -520,7 +624,7 @@ class TaskFactory {
JCTypeCast tree = (JCTypeCast) expr; JCTypeCast tree = (JCTypeCast) expr;
if (tree.clazz.hasTag(Tag.TYPEINTERSECTION)) { if (tree.clazz.hasTag(Tag.TYPEINTERSECTION)) {
field.type = attr.attribType(tree.clazz, field.type = attr.attribType(tree.clazz,
((JCClassDecl) roots.head.getTypeDecls().head).sym); ((JCClassDecl) root.getTypeDecls().head).sym);
} }
} }
} finally { } finally {

View file

@ -130,9 +130,9 @@ public class T7093325 extends ComboInstance<T7093325> {
@Override @Override
public void doWork() throws IOException { public void doWork() throws IOException {
verifyBytecode(newCompilationTask() newCompilationTask()
.withSourceFromTemplate(source_template) .withSourceFromTemplate(source_template)
.generate()); .generate(this::verifyBytecode);
} }
void verifyBytecode(Result<Iterable<? extends JavaFileObject>> result) { void verifyBytecode(Result<Iterable<? extends JavaFileObject>> result) {

View file

@ -254,9 +254,9 @@ public class IntersectionTypeCastTest extends ComboInstance<IntersectionTypeCast
@Override @Override
public void doWork() throws IOException { public void doWork() throws IOException {
check(newCompilationTask() newCompilationTask()
.withSourceFromTemplate(bodyTemplate) .withSourceFromTemplate(bodyTemplate)
.analyze()); .analyze(this::check);
} }
String bodyTemplate = "class Test {\n" + String bodyTemplate = "class Test {\n" +

View file

@ -154,10 +154,10 @@ public class InterfaceMethodHidingTest extends ComboInstance<InterfaceMethodHidi
@Override @Override
public void doWork() throws IOException { public void doWork() throws IOException {
check(newCompilationTask() newCompilationTask()
.withOption("-XDallowStaticInterfaceMethods") .withOption("-XDallowStaticInterfaceMethods")
.withSourceFromTemplate(template, this::returnExpr) .withSourceFromTemplate(template, this::returnExpr)
.analyze()); .analyze(this::check);
} }
ComboParameter returnExpr(String name) { ComboParameter returnExpr(String name) {

View file

@ -303,9 +303,9 @@ public class TestDefaultSuperCall extends ComboInstance<TestDefaultSuperCall> {
@Override @Override
public void doWork() throws IOException { public void doWork() throws IOException {
check(newCompilationTask() newCompilationTask()
.withSourceFromTemplate(template, this::methodName) .withSourceFromTemplate(template, this::methodName)
.analyze()); .analyze(this::check);
} }
ComboParameter methodName(String parameterName) { ComboParameter methodName(String parameterName) {

View file

@ -67,6 +67,7 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import javax.lang.model.element.Element; import javax.lang.model.element.Element;
import javax.swing.DefaultComboBoxModel; import javax.swing.DefaultComboBoxModel;
@ -287,61 +288,54 @@ public class CheckAttributedTree {
errWriter.println(file); errWriter.println(file);
fileCount.incrementAndGet(); fileCount.incrementAndGet();
NPETester p = new NPETester(); NPETester p = new NPETester();
p.test(read(file)); readAndCheck(file, p::test);
} catch (AttributionException e) { } catch (Throwable t) {
if (!quiet) { if (!quiet) {
error("Error attributing " + file + "\n" + e.getMessage()); error("Error checking " + file + "\n" + t.getMessage());
} }
} catch (IOException e) {
error("Error reading " + file + ": " + e);
} }
} }
/** /**
* Read a file. * Read and check a file.
* @param file the file to be read * @param file the file to be read
* @return the tree for the content of the file * @return the tree for the content of the file
* @throws IOException if any IO errors occur * @throws IOException if any IO errors occur
* @throws AttributionException if any errors occur while analyzing the file * @throws AttributionException if any errors occur while analyzing the file
*/ */
List<Pair<JCCompilationUnit, JCTree>> read(File file) throws IOException, AttributionException { void readAndCheck(File file, BiConsumer<JCCompilationUnit, JCTree> c) throws IOException {
try { Iterable<? extends JavaFileObject> files = fileManager().getJavaFileObjects(file);
Iterable<? extends JavaFileObject> files = fileManager().getJavaFileObjects(file); final List<Element> analyzedElems = new ArrayList<>();
final List<Element> analyzedElems = new ArrayList<>(); final List<CompilationUnitTree> trees = new ArrayList<>();
final List<CompilationUnitTree> trees = new ArrayList<>(); newCompilationTask()
Iterable<? extends Element> elems = newCompilationTask() .withWriter(pw)
.withWriter(pw) .withOption("--should-stop:at=ATTR")
.withOption("--should-stop:at=ATTR") .withOption("-XDverboseCompilePolicy")
.withOption("-XDverboseCompilePolicy") .withSource(files.iterator().next())
.withSource(files.iterator().next()) .withListener(new TaskListener() {
.withListener(new TaskListener() { public void started(TaskEvent e) {
public void started(TaskEvent e) { if (e.getKind() == TaskEvent.Kind.ANALYZE)
if (e.getKind() == TaskEvent.Kind.ANALYZE) analyzedElems.add(e.getTypeElement());
analyzedElems.add(e.getTypeElement()); }
}
public void finished(TaskEvent e) { public void finished(TaskEvent e) {
if (e.getKind() == Kind.PARSE) if (e.getKind() == Kind.PARSE)
trees.add(e.getCompilationUnit()); trees.add(e.getCompilationUnit());
} }
}).analyze().get(); }).analyze(res -> {
Iterable<? extends Element> elems = res.get();
if (!elems.iterator().hasNext()) if (!elems.iterator().hasNext())
throw new AttributionException("No results from analyze"); throw new AssertionError("No results from analyze");
List<Pair<JCCompilationUnit, JCTree>> res = new ArrayList<>();
for (CompilationUnitTree t : trees) { for (CompilationUnitTree t : trees) {
JCCompilationUnit cu = (JCCompilationUnit)t; JCCompilationUnit cu = (JCCompilationUnit)t;
for (JCTree def : cu.defs) { for (JCTree def : cu.defs) {
if (def.hasTag(CLASSDEF) && if (def.hasTag(CLASSDEF) &&
analyzedElems.contains(((JCTree.JCClassDecl)def).sym)) { analyzedElems.contains(((JCTree.JCClassDecl)def).sym)) {
res.add(new Pair<>(cu, def)); c.accept(cu, def);
} }
} }
} }
return res; });
}
catch (Throwable t) {
throw new AttributionException("Exception while attributing file: " + file);
}
} }
/** /**
@ -361,13 +355,11 @@ public class CheckAttributedTree {
* left uninitialized after attribution * left uninitialized after attribution
*/ */
private class NPETester extends TreeScanner { private class NPETester extends TreeScanner {
void test(List<Pair<JCCompilationUnit, JCTree>> trees) { void test(JCCompilationUnit cut, JCTree tree) {
for (Pair<JCCompilationUnit, JCTree> p : trees) { sourcefile = cut.sourcefile;
sourcefile = p.fst.sourcefile; endPosTable = cut.endPositions;
endPosTable = p.fst.endPositions; encl = new Info(tree, endPosTable);
encl = new Info(p.snd, endPosTable); tree.accept(this);
p.snd.accept(this);
}
} }
@Override @Override
@ -536,21 +528,6 @@ public class CheckAttributedTree {
} }
} }
/**
* Thrown when errors are found parsing a java file.
*/
private static class ParseException extends Exception {
ParseException(String msg) {
super(msg);
}
}
private static class AttributionException extends Exception {
AttributionException(String msg) {
super(msg);
}
}
/** /**
* GUI viewer for issues found by TreePosTester. The viewer provides a drop * GUI viewer for issues found by TreePosTester. The viewer provides a drop
* down list for selecting error conditions, a header area providing details * down list for selecting error conditions, a header area providing details

View file

@ -238,9 +238,9 @@ public class DiamondAndInnerClassTest extends ComboInstance<DiamondAndInnerClass
@Override @Override
public void doWork() throws IOException { public void doWork() throws IOException {
check(newCompilationTask() newCompilationTask()
.withSourceFromTemplate("#{DECL}") .withSourceFromTemplate("#{DECL}")
.analyze()); .analyze(this::check);
} }
void check(Result<?> res) { void check(Result<?> res) {

View file

@ -241,9 +241,9 @@ public class TestUncheckedCalls extends ComboInstance<TestUncheckedCalls> {
@Override @Override
public void doWork() throws Throwable { public void doWork() throws Throwable {
check(newCompilationTask() newCompilationTask()
.withSourceFromTemplate(sourceTemplate) .withSourceFromTemplate(sourceTemplate)
.analyze()); .analyze(this::check);
} }
void check(Result<Iterable<? extends Element>> result) { void check(Result<Iterable<? extends Element>> result) {

View file

@ -189,11 +189,11 @@ public class GenericOverrideTest extends ComboInstance<GenericOverrideTest> {
@Override @Override
public void doWork() throws IOException { public void doWork() throws IOException {
check(newCompilationTask() newCompilationTask()
.withOption("-XDuseUnsharedTable") //this test relies on predictable name indexes! .withOption("-XDuseUnsharedTable") //this test relies on predictable name indexes!
.withOptions(level.opts) .withOptions(level.opts)
.withSourceFromTemplate(template) .withSourceFromTemplate(template)
.analyze()); .analyze(this::check);
} }
void check(Result<?> res) { void check(Result<?> res) {

View file

@ -188,11 +188,11 @@ public class FunctionalInterfaceConversionTest extends ComboInstance<FunctionalI
@Override @Override
public void doWork() throws IOException { public void doWork() throws IOException {
check(newCompilationTask() newCompilationTask()
.withSourceFromTemplate("Sam", samSource) .withSourceFromTemplate("Sam", samSource)
.withSourceFromTemplate("PackageClass", pkgClassSource) .withSourceFromTemplate("PackageClass", pkgClassSource)
.withSourceFromTemplate("Client", clientSource, this::importStmt) .withSourceFromTemplate("Client", clientSource, this::importStmt)
.analyze()); .analyze(this::check);
} }
ComboParameter importStmt(String name) { ComboParameter importStmt(String name) {

View file

@ -246,9 +246,9 @@ public class LambdaParserTest extends ComboInstance<LambdaParserTest> {
@Override @Override
public void doWork() throws IOException { public void doWork() throws IOException {
check(newCompilationTask() newCompilationTask()
.withSourceFromTemplate(template) .withSourceFromTemplate(template)
.parse()); .parse(this::check);
} }
void check(Result<?> res) { void check(Result<?> res) {

View file

@ -203,9 +203,9 @@ public class MethodReferenceParserTest extends ComboInstance<MethodReferencePars
@Override @Override
public void doWork() throws IOException { public void doWork() throws IOException {
check(newCompilationTask() newCompilationTask()
.withSourceFromTemplate(template) .withSourceFromTemplate(template)
.parse()); .parse(this::check);
} }
void check(Result<?> res) { void check(Result<?> res) {

View file

@ -252,17 +252,16 @@ public class TestInvokeDynamic extends ComboInstance<TestInvokeDynamic> {
@Override @Override
public void doWork() throws IOException { public void doWork() throws IOException {
ComboTask comboTask = newCompilationTask() newCompilationTask()
.withOption("-g") .withOption("-g")
.withSourceFromTemplate(source_template); .withSourceFromTemplate(source_template)
.withListenerFactory(context -> {
JavacTaskImpl ct = (JavacTaskImpl)comboTask.getTask(); Symtab syms = Symtab.instance(context);
Context context = ct.getContext(); Names names = Names.instance(context);
Symtab syms = Symtab.instance(context); Types types = Types.instance(context);
Names names = Names.instance(context); return new Indifier(syms, names, types);
Types types = Types.instance(context); })
ct.addTaskListener(new Indifier(syms, names, types)); .generate(this::verifyBytecode);
verifyBytecode(comboTask.generate());
} }
void verifyBytecode(Result<Iterable<? extends JavaFileObject>> res) { void verifyBytecode(Result<Iterable<? extends JavaFileObject>> res) {

View file

@ -121,10 +121,10 @@ public class TestLambdaToMethodStats extends ComboInstance<TestLambdaToMethodSta
@Override @Override
public void doWork() throws IOException { public void doWork() throws IOException {
check(newCompilationTask() newCompilationTask()
.withOption("--debug:dumpLambdaToMethodStats") .withOption("--debug:dumpLambdaToMethodStats")
.withSourceFromTemplate(template) .withSourceFromTemplate(template)
.generate()); .generate(this::check);
} }
void check(Result<?> res) { void check(Result<?> res) {

View file

@ -193,9 +193,9 @@ public class TestLambdaBytecode extends ComboInstance<TestLambdaBytecode> {
@Override @Override
public void doWork() throws IOException { public void doWork() throws IOException {
verifyBytecode(newCompilationTask() newCompilationTask()
.withSourceFromTemplate(source_template) .withSourceFromTemplate(source_template)
.generate()); .generate(this::verifyBytecode);
} }
void verifyBytecode(Result<Iterable<? extends JavaFileObject>> res) { void verifyBytecode(Result<Iterable<? extends JavaFileObject>> res) {

View file

@ -208,10 +208,10 @@ public class StructuralMostSpecificTest extends ComboInstance<StructuralMostSpec
@Override @Override
public void doWork() throws Throwable { public void doWork() throws Throwable {
check(newCompilationTask() newCompilationTask()
.withSourceFromTemplate(sourceTemplate) .withSourceFromTemplate(sourceTemplate)
.withOption("--debug:verboseResolution=all,-predef,-internal,-object-init") .withOption("--debug:verboseResolution=all,-predef,-internal,-object-init")
.analyze()); .analyze(this::check);
} }
void check(Result<Iterable<? extends Element>> result) { void check(Result<Iterable<? extends Element>> result) {

View file

@ -281,14 +281,14 @@ public class TypeInferenceComboTest extends ComboInstance<TypeInferenceComboTest
@Override @Override
public void doWork() throws IOException { public void doWork() throws IOException {
Result<?> res = newCompilationTask() newCompilationTask()
.withSourceFromTemplate("Sam", sam_template, this::samClass) .withSourceFromTemplate("Sam", sam_template, this::samClass)
.withSourceFromTemplate("Client", client_template, this::clientContext) .withSourceFromTemplate("Client", client_template, this::clientContext)
.analyze(); .analyze(res -> {
if (res.hasErrors() == checkTypeInference()) {
if (res.hasErrors() == checkTypeInference()) { fail("Unexpected compilation output when compiling instance: " + res.compilationInfo());
fail("Unexpected compilation output when compiling instance: " + res.compilationInfo()); }
} });
} }
ComboParameter samClass(String parameterName) { ComboParameter samClass(String parameterName) {

View file

@ -26,8 +26,9 @@ package combo;
import com.sun.source.tree.CompilationUnitTree; import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.util.JavacTask; import com.sun.source.util.JavacTask;
import com.sun.source.util.TaskListener; import com.sun.source.util.TaskListener;
import com.sun.tools.javac.api.JavacTool; import com.sun.tools.javac.api.JavacTaskImpl;
import com.sun.tools.javac.util.Assert; import com.sun.tools.javac.util.Assert;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.List;
import combo.ComboParameter.Resolver; import combo.ComboParameter.Resolver;
@ -40,16 +41,11 @@ import javax.tools.SimpleJavaFileObject;
import java.io.IOException; import java.io.IOException;
import java.io.Writer; import java.io.Writer;
import java.net.URI; import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
/** /**
* This class represents a compilation task associated with a combo test instance. This is a small * This class represents a compilation task associated with a combo test instance. This is a small
@ -73,8 +69,8 @@ public class ComboTask {
/** Listeners associated with this task. */ /** Listeners associated with this task. */
private List<TaskListener> listeners = List.nil(); private List<TaskListener> listeners = List.nil();
/** Underlying javac task object. */ /** Listener factories associated with this task. */
private JavacTask task; private List<Function<Context, TaskListener>> listenerFactories = List.nil();
/** Combo execution environment. */ /** Combo execution environment. */
private ComboTestHelper<?>.Env env; private ComboTestHelper<?>.Env env;
@ -169,78 +165,56 @@ public class ComboTask {
return this; return this;
} }
/**
* Add a task listener factory to this task.
*/
public ComboTask withListenerFactory(Function<Context, TaskListener> factory) {
listenerFactories = listenerFactories.prepend(factory);
return this;
}
/** /**
* Parse the sources associated with this task. * Parse the sources associated with this task.
*/ */
public Result<Iterable<? extends CompilationUnitTree>> parse() throws IOException { public void parse(Consumer<Result<Iterable<? extends CompilationUnitTree>>> c) {
return new Result<>(getTask().parse()); doRunTest(c, JavacTask::parse);
} }
/** /**
* Parse and analyzes the sources associated with this task. * Parse and analyzes the sources associated with this task.
*/ */
public Result<Iterable<? extends Element>> analyze() throws IOException { public void analyze(Consumer<Result<Iterable<? extends Element>>> c) {
return new Result<>(getTask().analyze()); doRunTest(c, JavacTask::analyze);
} }
/** /**
* Parse, analyze and perform code generation for the sources associated with this task. * Parse, analyze and perform code generation for the sources associated with this task.
*/ */
public Result<Iterable<? extends JavaFileObject>> generate() throws IOException { public void generate(Consumer<Result<Iterable<? extends JavaFileObject>>> c) {
return new Result<>(getTask().generate()); doRunTest(c, JavacTask::generate);
} }
/** private <V> void doRunTest(Consumer<Result<Iterable<? extends V>>> c,
* Parse, analyze, perform code generation for the sources associated with this task and finally Convertor<V> task2Data) {
* executes them env.pool().getTask(out, env.fileManager(),
*/ diagsCollector, options, null, sources, task -> {
public <Z> Optional<Z> execute(Function<ExecutionTask, Z> executionFunc) throws IOException { try {
Result<Iterable<? extends JavaFileObject>> generationResult = generate(); for (TaskListener l : listeners) {
Iterable<? extends JavaFileObject> jfoIterable = generationResult.get(); task.addTaskListener(l);
if (generationResult.hasErrors()) { }
// we have nothing else to do for (Function<Context, TaskListener> f : listenerFactories) {
return Optional.empty(); task.addTaskListener(f.apply(((JavacTaskImpl) task).getContext()));
} }
java.util.List<URL> urlList = new ArrayList<>(); c.accept(new Result<>(task2Data.convert(task)));
for (JavaFileObject jfo : jfoIterable) { return null;
String urlStr = jfo.toUri().toURL().toString(); } catch (IOException ex) {
urlStr = urlStr.substring(0, urlStr.length() - jfo.getName().length()); throw new AssertionError(ex);
urlList.add(new URL(urlStr)); }
} });
return Optional.of(
executionFunc.apply(
new ExecutionTask(new URLClassLoader(urlList.toArray(new URL[urlList.size()])))));
} }
/** interface Convertor<V> {
* Fork a new compilation task; if possible the compilation context from previous executions is public Iterable<? extends V> convert(JavacTask task) throws IOException;
* retained (see comments in ReusableContext as to when it's safe to do so); otherwise a brand
* new context is created.
*/
public JavacTask getTask() {
if (task == null) {
ReusableContext context = env.context();
String opts = options == null ? "" :
StreamSupport.stream(options.spliterator(), false).collect(Collectors.joining());
context.clear();
if (!context.polluted && (context.opts == null || context.opts.equals(opts))) {
//we can reuse former context
env.info().ctxReusedCount++;
} else {
env.info().ctxDroppedCount++;
//it's not safe to reuse context - create a new one
context = env.setContext(new ReusableContext());
}
context.opts = opts;
JavacTask javacTask = ((JavacTool)env.javaCompiler()).getTask(out, env.fileManager(),
diagsCollector, options, null, sources, context);
javacTask.setTaskListener(context);
for (TaskListener l : listeners) {
javacTask.addTaskListener(l);
}
task = javacTask;
}
return task;
} }
/** /**

View file

@ -28,6 +28,7 @@ import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider; import javax.tools.ToolProvider;
import java.io.IOException; import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -38,6 +39,12 @@ import java.util.function.Consumer;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.function.Supplier; import java.util.function.Supplier;
import javax.tools.DiagnosticListener;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import com.sun.source.util.JavacTask;
import com.sun.tools.javac.api.JavacTaskPool;
/** /**
* An helper class for defining combinatorial (aka "combo" tests). A combo test is made up of one * An helper class for defining combinatorial (aka "combo" tests). A combo test is made up of one
@ -93,8 +100,8 @@ public class ComboTestHelper<X extends ComboInstance<X>> {
/** Shared file manager used across all combo test instances. */ /** Shared file manager used across all combo test instances. */
StandardJavaFileManager fm = comp.getStandardFileManager(null, null, null); StandardJavaFileManager fm = comp.getStandardFileManager(null, null, null);
/** Shared context used across all combo instances. */ /** JavacTask pool shared across all combo instances. */
ReusableContext context = new ReusableContext(); JavacTaskPool pool = new JavacTaskPool(1);
/** /**
* Set failure mode for this combo test. * Set failure mode for this combo test.
@ -248,7 +255,7 @@ public class ComboTestHelper<X extends ComboInstance<X>> {
} catch (IOException ex) { } catch (IOException ex) {
throw new AssertionError("Failure when closing down shared file manager; ", ex); throw new AssertionError("Failure when closing down shared file manager; ", ex);
} finally { } finally {
info.dump(); info.dump(this);
} }
} }
@ -375,19 +382,16 @@ public class ComboTestHelper<X extends ComboInstance<X>> {
int passCount; int passCount;
int comboCount; int comboCount;
int skippedCount; int skippedCount;
int ctxReusedCount;
int ctxDroppedCount;
Optional<String> lastFailure = Optional.empty(); Optional<String> lastFailure = Optional.empty();
Optional<Throwable> lastError = Optional.empty(); Optional<Throwable> lastError = Optional.empty();
void dump() { void dump(ComboTestHelper<?> helper) {
System.err.println(String.format("%d total checks executed", comboCount)); System.err.println(String.format("%d total checks executed", comboCount));
System.err.println(String.format("%d successes found", passCount)); System.err.println(String.format("%d successes found", passCount));
System.err.println(String.format("%d failures found", failCount)); System.err.println(String.format("%d failures found", failCount));
System.err.println(String.format("%d errors found", errCount)); System.err.println(String.format("%d errors found", errCount));
System.err.println(String.format("%d skips found", skippedCount)); System.err.println(String.format("%d skips found", skippedCount));
System.err.println(String.format("%d contexts shared", ctxReusedCount)); helper.pool.printStatistics(System.err);
System.err.println(String.format("%d contexts dropped", ctxDroppedCount));
} }
public boolean hasFailures() { public boolean hasFailures() {
@ -400,7 +404,7 @@ public class ComboTestHelper<X extends ComboInstance<X>> {
} }
/** /**
* THe execution environment for a given combo test instance. An environment contains the * The execution environment for a given combo test instance. An environment contains the
* bindings for all the dimensions, along with the combo parameter cache (this is non-empty * bindings for all the dimensions, along with the combo parameter cache (this is non-empty
* only if one or more dimensions are subclasses of the {@code ComboParameter} interface). * only if one or more dimensions are subclasses of the {@code ComboParameter} interface).
*/ */
@ -430,12 +434,8 @@ public class ComboTestHelper<X extends ComboInstance<X>> {
return comp; return comp;
} }
ReusableContext context() { JavacTaskPool pool() {
return context; return pool;
}
ReusableContext setContext(ReusableContext context) {
return ComboTestHelper.this.context = context;
} }
} }
} }

View file

@ -1,229 +0,0 @@
/*
* Copyright (c) 2015, 2017, 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.
*
* 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 combo;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.util.JavacTask;
import com.sun.source.util.TaskEvent;
import com.sun.source.util.TaskEvent.Kind;
import com.sun.source.util.TaskListener;
import com.sun.source.util.TreeScanner;
import com.sun.tools.javac.api.MultiTaskListener;
import com.sun.tools.javac.code.Kinds;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symtab;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Type.ClassType;
import com.sun.tools.javac.code.TypeTag;
import com.sun.tools.javac.code.Types;
import com.sun.tools.javac.comp.Annotate;
import com.sun.tools.javac.comp.Check;
import com.sun.tools.javac.comp.CompileStates;
import com.sun.tools.javac.comp.Enter;
import com.sun.tools.javac.comp.Modules;
import com.sun.tools.javac.main.Arguments;
import com.sun.tools.javac.main.JavaCompiler;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.Log;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticListener;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import java.util.HashSet;
import java.util.Set;
/**
* A reusable context is a context that can be used safely across multiple compilation rounds
* arising from execution of a combo test. It achieves reuse by replacing some components
* (most notably JavaCompiler and Log) with reusable counterparts, and by exposing a method
* to cleanup leftovers from previous compilation.
* <p>
* There are, however, situations in which reusing the context is not safe: (i) when different
* compilations are using different sets of compiler options (as most option values are cached
* inside components themselves) and (ii) when the compilation unit happens to redefine classes
* in the java.* packages.
*/
class ReusableContext extends Context implements TaskListener {
Set<CompilationUnitTree> roots = new HashSet<>();
String opts;
boolean polluted = false;
ReusableContext() {
super();
put(Log.logKey, ReusableLog.factory);
put(JavaCompiler.compilerKey, ReusableJavaCompiler.factory);
}
void clear() {
drop(Arguments.argsKey);
drop(DiagnosticListener.class);
drop(Log.outKey);
drop(Log.errKey);
drop(JavaFileManager.class);
drop(JavacTask.class);
if (ht.get(Log.logKey) instanceof ReusableLog) {
//log already inited - not first round
((ReusableLog)Log.instance(this)).clear();
Enter.instance(this).newRound();
((ReusableJavaCompiler)ReusableJavaCompiler.instance(this)).clear();
Types.instance(this).newRound();
Check.instance(this).newRound();
Modules.instance(this).newRound();
Annotate.instance(this).newRound();
CompileStates.instance(this).clear();
MultiTaskListener.instance(this).clear();
//find if any of the roots have redefined java.* classes
Symtab syms = Symtab.instance(this);
pollutionScanner.scan(roots, syms);
roots.clear();
}
}
/**
* This scanner detects as to whether the shared context has been polluted. This happens
* whenever a compiled program redefines a core class (in 'java.*' package) or when
* (typically because of cyclic inheritance) the symbol kind of a core class has been touched.
*/
TreeScanner<Void, Symtab> pollutionScanner = new TreeScanner<Void, Symtab>() {
@Override
public Void visitClass(ClassTree node, Symtab syms) {
Symbol sym = ((JCClassDecl)node).sym;
if (sym != null) {
syms.removeClass(sym.packge().modle, sym.flatName());
Type sup = supertype(sym);
if (isCoreClass(sym) ||
(sup != null && isCoreClass(sup.tsym) && sup.tsym.kind != Kinds.Kind.TYP)) {
polluted = true;
}
}
return super.visitClass(node, syms);
}
private boolean isCoreClass(Symbol s) {
return s.flatName().toString().startsWith("java.");
}
private Type supertype(Symbol s) {
if (s.type == null ||
!s.type.hasTag(TypeTag.CLASS)) {
return null;
} else {
ClassType ct = (ClassType)s.type;
return ct.supertype_field;
}
}
};
@Override
public void finished(TaskEvent e) {
if (e.getKind() == Kind.PARSE) {
roots.add(e.getCompilationUnit());
}
}
@Override
public void started(TaskEvent e) {
//do nothing
}
<T> void drop(Key<T> k) {
ht.remove(k);
}
<T> void drop(Class<T> c) {
ht.remove(key(c));
}
/**
* Reusable JavaCompiler; exposes a method to clean up the component from leftovers associated with
* previous compilations.
*/
static class ReusableJavaCompiler extends JavaCompiler {
static Factory<JavaCompiler> factory = ReusableJavaCompiler::new;
ReusableJavaCompiler(Context context) {
super(context);
}
@Override
public void close() {
//do nothing
}
void clear() {
newRound();
}
@Override
protected void checkReusable() {
//do nothing - it's ok to reuse the compiler
}
}
/**
* Reusable Log; exposes a method to clean up the component from leftovers associated with
* previous compilations.
*/
static class ReusableLog extends Log {
static Factory<Log> factory = ReusableLog::new;
Context context;
ReusableLog(Context context) {
super(context);
this.context = context;
}
void clear() {
recorded.clear();
sourceMap.clear();
nerrors = 0;
nwarnings = 0;
//Set a fake listener that will lazily lookup the context for the 'real' listener. Since
//this field is never updated when a new task is created, we cannot simply reset the field
//or keep old value. This is a hack to workaround the limitations in the current infrastructure.
diagListener = new DiagnosticListener<JavaFileObject>() {
DiagnosticListener<JavaFileObject> cachedListener;
@Override
@SuppressWarnings("unchecked")
public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
if (cachedListener == null) {
cachedListener = context.get(DiagnosticListener.class);
}
cachedListener.report(diagnostic);
}
};
}
}
}

View file

@ -128,9 +128,9 @@ public class DisjunctiveTypeWellFormednessTest extends ComboInstance<Disjunctive
@Override @Override
public void doWork() throws IOException { public void doWork() throws IOException {
check(newCompilationTask() newCompilationTask()
.withSourceFromTemplate(template) .withSourceFromTemplate(template)
.analyze()); .analyze(this::check);
} }
void check(Result<?> res) { void check(Result<?> res) {

View file

@ -100,13 +100,14 @@ public class BitWiseOperators extends ComboInstance<BitWiseOperators> {
@Override @Override
public void doWork() throws IOException { public void doWork() throws IOException {
Result<?> res = newCompilationTask() newCompilationTask()
.withSourceFromTemplate(template) .withSourceFromTemplate(template)
.analyze(); .analyze(res -> {
if (res.hasErrors() == OperandType.compatible(opTypes[0], opTypes[1])) { if (res.hasErrors() == OperandType.compatible(opTypes[0], opTypes[1])) {
fail("Unexpected behavior. Type1: " + opTypes[0] + fail("Unexpected behavior. Type1: " + opTypes[0] +
"; type2: " + opTypes[1] + "; type2: " + opTypes[1] +
"; " + res.compilationInfo()); "; " + res.compilationInfo());
} }
});
} }
} }

View file

@ -0,0 +1,86 @@
/*
* Copyright (c) 2013, 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.
*
* 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.
*/
/*
* @test
* @bug 8186694
* @summary Verify that taking a Scope inside a class header
* does not taint internal structures
* @modules jdk.compiler
* @run main ScopeClassHeaderTest
*/
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.Scope;
import com.sun.source.util.JavacTask;
import com.sun.source.util.TreePathScanner;
import com.sun.source.util.Trees;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.SimpleJavaFileObject;
import javax.tools.ToolProvider;
import com.sun.source.tree.IdentifierTree;
public class ScopeClassHeaderTest {
public static void main(String... args) throws Exception {
verifyScopeForClassHeader();
}
private static void verifyScopeForClassHeader() throws Exception {
JavaCompiler tool = ToolProvider.getSystemJavaCompiler();
JavaFileObject source = new SimpleJavaFileObject(URI.create("mem://Test.java"), Kind.SOURCE) {
@Override public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
return "import java.util.*; class O { public void m() { class X<T extends ArrayList> { public void test() { String o; } } } }";
}
@Override public boolean isNameCompatible(String simpleName, Kind kind) {
return !"module-info".equals(simpleName);
}
};
Iterable<? extends JavaFileObject> fos = Collections.singletonList(source);
JavacTask task = (JavacTask) tool.getTask(null, null, null, new ArrayList<String>(), null, fos);
final Trees trees = Trees.instance(task);
CompilationUnitTree cu = task.parse().iterator().next();
task.analyze();
new TreePathScanner<Void, Void>() {
@Override
public Void visitIdentifier(IdentifierTree node, Void p) {
if (node.getName().contentEquals("ArrayList") || node.getName().contentEquals("String")) {
Scope scope = trees.getScope(getCurrentPath());
System.err.println("scope: " + scope);
}
return super.visitIdentifier(node, p);
}
}.scan(cu, null);
}
}

View file

@ -0,0 +1,91 @@
/*
* Copyright (c) 2017, 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.
*
* 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.
*/
/**
* @test
* @bug 8186694
* @summary Check that JavacTaskPool reuses JavacTask internals when it should
* @modules jdk.compiler/com.sun.tools.javac.api
* @run main JavacTaskPoolTest
*/
import java.util.List;
import javax.lang.model.util.Types;
import com.sun.tools.javac.api.JavacTaskPool;
public class JavacTaskPoolTest {
public static void main(String... args) throws Exception {
new JavacTaskPoolTest().run();
}
void run() throws Exception {
JavacTaskPool pool = new JavacTaskPool(2);
Types tps1 = pool.getTask(null, null, null, List.of("-XDone"), null, null, task -> {
task.getElements(); //initialize
return task.getTypes();
});
Types tps2 = pool.getTask(null, null, null, List.of("-XDone"), null, null, task -> {
task.getElements(); //initialize
return task.getTypes();
});
assertSame(tps1, tps2);
Types tps3 = pool.getTask(null, null, null, List.of("-XDtwo"), null, null, task -> {
task.getElements(); //initialize
return task.getTypes();
});
assertNotSame(tps1, tps3);
Types tps4 = pool.getTask(null, null, null, List.of("-XDthree"), null, null, task -> {
task.getElements(); //initialize
return task.getTypes();
});
assertNotSame(tps1, tps4);
assertNotSame(tps3, tps4);
Types tps5 = pool.getTask(null, null, null, List.of("-XDone"), null, null, task -> {
task.getElements(); //initialize
return task.getTypes();
});
assertNotSame(tps1, tps5);
}
void assertSame(Object expected, Object actual) {
if (expected != actual) {
throw new IllegalStateException("expected=" + expected + "; actual=" + actual);
}
}
void assertNotSame(Object expected, Object actual) {
if (expected == actual) {
throw new IllegalStateException("expected=" + expected + "; actual=" + actual);
}
}
}

View file

@ -224,9 +224,9 @@ public class T7042566 extends ComboInstance<T7042566> {
@Override @Override
public void doWork() throws IOException { public void doWork() throws IOException {
check(newCompilationTask() newCompilationTask()
.withSourceFromTemplate(source_template, this::getMethodDecl) .withSourceFromTemplate(source_template, this::getMethodDecl)
.generate()); .generate(this::check);
} }
ComboParameter getMethodDecl(String parameterName) { ComboParameter getMethodDecl(String parameterName) {

View file

@ -231,12 +231,12 @@ public class Warn4 extends ComboInstance<Warn4> {
@Override @Override
public void doWork() throws IOException { public void doWork() throws IOException {
check(newCompilationTask() newCompilationTask()
.withOption("-Xlint:unchecked") .withOption("-Xlint:unchecked")
.withOption("-source") .withOption("-source")
.withOption(sourceLevel.sourceKey) .withOption(sourceLevel.sourceKey)
.withSourceFromTemplate(template) .withSourceFromTemplate(template)
.analyze()); .analyze(this::check);
} }
void check(Result<?> res) { void check(Result<?> res) {

View file

@ -235,12 +235,12 @@ public class Warn5 extends ComboInstance<Warn5> {
@Override @Override
public void doWork() throws IOException { public void doWork() throws IOException {
check(newCompilationTask() newCompilationTask()
.withOption(xlint.getXlintOption()) .withOption(xlint.getXlintOption())
.withOption("-source") .withOption("-source")
.withOption(sourceLevel.sourceKey) .withOption(sourceLevel.sourceKey)
.withSourceFromTemplate(template) .withSourceFromTemplate(template)
.analyze()); .analyze(this::check);
} }
void check(Result<?> res) { void check(Result<?> res) {