8145239: JShell: throws AssertionError when replace classes with some methods which depends on these classes

Reviewed-by: rfield
This commit is contained in:
Shinya Yoshida 2015-12-29 21:27:25 -08:00 committed by Robert Field
parent 47dafa22a0
commit b592106de3
7 changed files with 133 additions and 88 deletions

View File

@ -190,7 +190,7 @@ class Eval {
private List<SnippetEvent> processVariables(String userSource, List<? extends Tree> units, String compileSource, ParseTask pt) {
List<SnippetEvent> allEvents = new ArrayList<>();
TreeDissector dis = new TreeDissector(pt);
TreeDissector dis = TreeDissector.createByFirstClass(pt);
for (Tree unitTree : units) {
VariableTree vt = (VariableTree) unitTree;
String name = vt.getName().toString();
@ -295,7 +295,7 @@ class Eval {
TreeDependencyScanner tds = new TreeDependencyScanner();
tds.scan(unitTree);
TreeDissector dis = new TreeDissector(pt);
TreeDissector dis = TreeDissector.createByFirstClass(pt);
ClassTree klassTree = (ClassTree) unitTree;
String name = klassTree.getSimpleName().toString();
@ -354,7 +354,7 @@ class Eval {
tds.scan(unitTree);
MethodTree mt = (MethodTree) unitTree;
TreeDissector dis = new TreeDissector(pt);
TreeDissector dis = TreeDissector.createByFirstClass(pt);
DiagList modDiag = modifierDiagnostics(mt.getModifiers(), dis, true);
if (modDiag.hasErrors()) {
return compileFailResult(modDiag, userSource);
@ -418,8 +418,8 @@ class Eval {
private ExpressionInfo typeOfExpression(String expression) {
Wrap guts = Wrap.methodReturnWrap(expression);
TaskFactory.AnalyzeTask at = trialCompile(guts);
if (!at.hasErrors() && at.cuTree() != null) {
return new TreeDissector(at)
if (!at.hasErrors() && at.firstCuTree() != null) {
return TreeDissector.createByFirstClass(at)
.typeOfReturnStatement(at.messages(), state.maps::fullClassNameAndPackageToClass);
}
return null;
@ -513,13 +513,17 @@ class Eval {
ins.stream().forEach(u -> u.initialize(ins));
AnalyzeTask at = state.taskFactory.new AnalyzeTask(ins);
ins.stream().forEach(u -> u.setDiagnostics(at));
// corral any Snippets that need it
if (ins.stream().filter(u -> u.corralIfNeeded(ins)).count() > 0) {
AnalyzeTask cat;
if (ins.stream().anyMatch(u -> u.corralIfNeeded(ins))) {
// if any were corralled, re-analyze everything
AnalyzeTask cat = state.taskFactory.new AnalyzeTask(ins);
cat = state.taskFactory.new AnalyzeTask(ins);
ins.stream().forEach(u -> u.setCorralledDiagnostics(cat));
} else {
cat = at;
}
ins.stream().forEach(u -> u.setStatus());
ins.stream().forEach(u -> u.setStatus(cat));
// compile and load the legit snippets
boolean success;
while (true) {

View File

@ -239,7 +239,7 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
private List<Suggestion> computeSuggestions(OuterWrap code, int cursor, int[] anchor) {
AnalyzeTask at = proc.taskFactory.new AnalyzeTask(code);
SourcePositions sp = at.trees().getSourcePositions();
CompilationUnitTree topLevel = at.cuTree();
CompilationUnitTree topLevel = at.firstCuTree();
List<Suggestion> result = new ArrayList<>();
TreePath tp = pathFor(topLevel, sp, code.snippetIndexToWrapIndex(cursor));
if (tp != null) {
@ -976,7 +976,7 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis {
OuterWrap codeWrap = wrapInClass(Wrap.methodWrap(code));
AnalyzeTask at = proc.taskFactory.new AnalyzeTask(codeWrap);
SourcePositions sp = at.trees().getSourcePositions();
CompilationUnitTree topLevel = at.cuTree();
CompilationUnitTree topLevel = at.firstCuTree();
TreePath tp = pathFor(topLevel, sp, codeWrap.snippetIndexToWrapIndex(cursor));
if (tp == null)

View File

@ -56,6 +56,7 @@ import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toList;
import java.util.stream.Stream;
import javax.lang.model.util.Elements;
import javax.tools.FileObject;
@ -196,7 +197,7 @@ class TaskFactory {
*/
class ParseTask extends BaseTask {
private final CompilationUnitTree cut;
private final Iterable<? extends CompilationUnitTree> cuts;
private final List<? extends Tree> units;
ParseTask(final String source) {
@ -204,16 +205,13 @@ class TaskFactory {
new StringSourceHandler(),
"-XDallowStringFolding=false", "-proc:none");
ReplParserFactory.instance(getContext());
Iterable<? extends CompilationUnitTree> asts = parse();
Iterator<? extends CompilationUnitTree> it = asts.iterator();
if (it.hasNext()) {
this.cut = it.next();
List<? extends ImportTree> imps = cut.getImports();
this.units = !imps.isEmpty() ? imps : cut.getTypeDecls();
} else {
this.cut = null;
this.units = Collections.emptyList();
}
cuts = parse();
units = Util.stream(cuts)
.flatMap(cut -> {
List<? extends ImportTree> imps = cut.getImports();
return (!imps.isEmpty() ? imps : cut.getTypeDecls()).stream();
})
.collect(toList());
}
private Iterable<? extends CompilationUnitTree> parse() {
@ -229,8 +227,8 @@ class TaskFactory {
}
@Override
CompilationUnitTree cuTree() {
return cut;
Iterable<? extends CompilationUnitTree> cuTrees() {
return cuts;
}
}
@ -239,7 +237,7 @@ class TaskFactory {
*/
class AnalyzeTask extends BaseTask {
private final CompilationUnitTree cut;
private final Iterable<? extends CompilationUnitTree> cuts;
AnalyzeTask(final OuterWrap wrap) {
this(Stream.of(wrap),
@ -255,14 +253,7 @@ class TaskFactory {
<T>AnalyzeTask(final Stream<T> stream, SourceHandler<T> sourceHandler,
String... extraOptions) {
super(stream, sourceHandler, extraOptions);
Iterator<? extends CompilationUnitTree> cuts = analyze().iterator();
if (cuts.hasNext()) {
this.cut = cuts.next();
//proc.debug("AnalyzeTask element=%s cutp=%s cut=%s\n", e, cutp, cut);
} else {
this.cut = null;
//proc.debug("AnalyzeTask -- no elements -- %s\n", getDiagnostics());
}
cuts = analyze();
}
private Iterable<? extends CompilationUnitTree> analyze() {
@ -276,8 +267,8 @@ class TaskFactory {
}
@Override
CompilationUnitTree cuTree() {
return cut;
Iterable<? extends CompilationUnitTree> cuTrees() {
return cuts;
}
Elements getElements() {
@ -332,7 +323,7 @@ class TaskFactory {
}
@Override
CompilationUnitTree cuTree() {
Iterable<? extends CompilationUnitTree> cuTrees() {
throw new UnsupportedOperationException("Not supported.");
}
}
@ -362,7 +353,11 @@ class TaskFactory {
compilationUnits, context);
}
abstract CompilationUnitTree cuTree();
abstract Iterable<? extends CompilationUnitTree> cuTrees();
CompilationUnitTree firstCuTree() {
return cuTrees().iterator().next();
}
Diag diag(Diagnostic<? extends JavaFileObject> diag) {
return sourceHandler.diag(diag);

View File

@ -48,7 +48,10 @@ import jdk.jshell.Wrap.Range;
import java.util.List;
import java.util.Locale;
import java.util.function.BinaryOperator;
import java.util.function.Predicate;
import java.util.stream.Stream;
import javax.lang.model.type.TypeMirror;
import jdk.jshell.Util.Pair;
/**
* Utilities for analyzing compiler API parse trees.
@ -68,23 +71,48 @@ class TreeDissector {
}
private final TaskFactory.BaseTask bt;
private ClassTree firstClass;
private final ClassTree targetClass;
private final CompilationUnitTree targetCompilationUnit;
private SourcePositions theSourcePositions = null;
TreeDissector(TaskFactory.BaseTask bt) {
private TreeDissector(TaskFactory.BaseTask bt, CompilationUnitTree targetCompilationUnit, ClassTree targetClass) {
this.bt = bt;
this.targetCompilationUnit = targetCompilationUnit;
this.targetClass = targetClass;
}
static TreeDissector createByFirstClass(TaskFactory.BaseTask bt) {
Pair<CompilationUnitTree, ClassTree> pair = classes(bt.firstCuTree())
.findFirst().orElseGet(() -> new Pair<>(bt.firstCuTree(), null));
ClassTree firstClass() {
if (firstClass == null) {
firstClass = computeFirstClass();
}
return firstClass;
return new TreeDissector(bt, pair.first, pair.second);
}
CompilationUnitTree cuTree() {
return bt.cuTree();
private static final Predicate<? super Tree> isClassOrInterface =
t -> t.getKind() == Tree.Kind.CLASS || t.getKind() == Tree.Kind.INTERFACE;
private static Stream<Pair<CompilationUnitTree, ClassTree>> classes(CompilationUnitTree cut) {
return cut == null
? Stream.empty()
: cut.getTypeDecls().stream()
.filter(isClassOrInterface)
.map(decl -> new Pair<>(cut, (ClassTree)decl));
}
private static Stream<Pair<CompilationUnitTree, ClassTree>> classes(Iterable<? extends CompilationUnitTree> cuts) {
return Util.stream(cuts)
.flatMap(TreeDissector::classes);
}
static TreeDissector createBySnippet(TaskFactory.BaseTask bt, Snippet si) {
String name = si.className();
Pair<CompilationUnitTree, ClassTree> pair = classes(bt.cuTrees())
.filter(p -> p.second.getSimpleName().contentEquals(name))
.findFirst().orElseThrow(() ->
new IllegalArgumentException("Class " + name + " is not found."));
return new TreeDissector(bt, pair.first, pair.second);
}
Types types() {
@ -103,11 +131,11 @@ class TreeDissector {
}
int getStartPosition(Tree tree) {
return (int) getSourcePositions().getStartPosition(cuTree(), tree);
return (int) getSourcePositions().getStartPosition(targetCompilationUnit, tree);
}
int getEndPosition(Tree tree) {
return (int) getSourcePositions().getEndPosition(cuTree(), tree);
return (int) getSourcePositions().getEndPosition(targetCompilationUnit, tree);
}
Range treeToRange(Tree tree) {
@ -134,9 +162,9 @@ class TreeDissector {
}
Tree firstClassMember() {
if (firstClass() != null) {
if (targetClass != null) {
//TODO: missing classes
for (Tree mem : firstClass().getMembers()) {
for (Tree mem : targetClass.getMembers()) {
if (mem.getKind() == Tree.Kind.VARIABLE) {
return mem;
}
@ -152,8 +180,8 @@ class TreeDissector {
}
StatementTree firstStatement() {
if (firstClass() != null) {
for (Tree mem : firstClass().getMembers()) {
if (targetClass != null) {
for (Tree mem : targetClass.getMembers()) {
if (mem.getKind() == Tree.Kind.METHOD) {
MethodTree mt = (MethodTree) mem;
if (isDoIt(mt.getName())) {
@ -169,8 +197,8 @@ class TreeDissector {
}
VariableTree firstVariable() {
if (firstClass() != null) {
for (Tree mem : firstClass().getMembers()) {
if (targetClass != null) {
for (Tree mem : targetClass.getMembers()) {
if (mem.getKind() == Tree.Kind.VARIABLE) {
VariableTree vt = (VariableTree) mem;
return vt;
@ -180,17 +208,6 @@ class TreeDissector {
return null;
}
private ClassTree computeFirstClass() {
if (cuTree() == null) {
return null;
}
for (Tree decl : cuTree().getTypeDecls()) {
if (decl.getKind() == Tree.Kind.CLASS || decl.getKind() == Tree.Kind.INTERFACE) {
return (ClassTree) decl;
}
}
return null;
}
ExpressionInfo typeOfReturnStatement(JavacMessages messages, BinaryOperator<String> fullClassNameAndPackageToClass) {
ExpressionInfo ei = new ExpressionInfo();
@ -198,7 +215,7 @@ class TreeDissector {
if (unitTree instanceof ReturnTree) {
ei.tree = ((ReturnTree) unitTree).getExpression();
if (ei.tree != null) {
TreePath viPath = trees().getPath(cuTree(), ei.tree);
TreePath viPath = trees().getPath(targetCompilationUnit, ei.tree);
if (viPath != null) {
TypeMirror tm = trees().getTypeMirror(viPath);
if (tm != null) {

View File

@ -225,7 +225,7 @@ final class Unit {
return false;
}
void setStatus() {
void setStatus(AnalyzeTask at) {
if (!compilationDiagnostics.hasErrors()) {
status = VALID;
} else if (isRecoverable()) {
@ -237,7 +237,7 @@ final class Unit {
} else {
status = REJECTED;
}
checkForOverwrite();
checkForOverwrite(at);
state.debug(DBG_GEN, "setStatus() %s - status: %s\n",
si, status);
@ -361,17 +361,18 @@ final class Unit {
si, status, unresolved);
}
private void checkForOverwrite() {
private void checkForOverwrite(AnalyzeTask at) {
secondaryEvents = new ArrayList<>();
if (replaceOldEvent != null) secondaryEvents.add(replaceOldEvent);
// Defined methods can overwrite methods of other (equivalent) snippets
if (si.kind() == Kind.METHOD && status.isDefined) {
String oqpt = ((MethodSnippet) si).qualifiedParameterTypes();
String nqpt = computeQualifiedParameterTypes(si);
MethodSnippet msi = (MethodSnippet)si;
String oqpt = msi.qualifiedParameterTypes();
String nqpt = computeQualifiedParameterTypes(at, msi);
if (!nqpt.equals(oqpt)) {
((MethodSnippet) si).setQualifiedParamaterTypes(nqpt);
Status overwrittenStatus = overwriteMatchingMethod(si);
msi.setQualifiedParamaterTypes(nqpt);
Status overwrittenStatus = overwriteMatchingMethod(msi);
if (overwrittenStatus != null) {
prevStatus = overwrittenStatus;
signatureChanged = true;
@ -383,19 +384,19 @@ final class Unit {
// Check if there is a method whose user-declared parameter types are
// different (and thus has a different snippet) but whose compiled parameter
// types are the same. if so, consider it an overwrite replacement.
private Status overwriteMatchingMethod(Snippet si) {
String qpt = ((MethodSnippet) si).qualifiedParameterTypes();
private Status overwriteMatchingMethod(MethodSnippet msi) {
String qpt = msi.qualifiedParameterTypes();
// Look through all methods for a method of the same name, with the
// same computed qualified parameter types
Status overwrittenStatus = null;
for (MethodSnippet sn : state.methods()) {
if (sn != null && sn != si && sn.status().isActive && sn.name().equals(si.name())) {
if (sn != null && sn != msi && sn.status().isActive && sn.name().equals(msi.name())) {
if (qpt.equals(sn.qualifiedParameterTypes())) {
overwrittenStatus = sn.status();
SnippetEvent se = new SnippetEvent(
sn, overwrittenStatus, OVERWRITTEN,
false, si, null, null);
false, msi, null, null);
sn.setOverwritten();
secondaryEvents.add(se);
state.debug(DBG_EVNT,
@ -408,20 +409,16 @@ final class Unit {
return overwrittenStatus;
}
private String computeQualifiedParameterTypes(Snippet si) {
MethodSnippet msi = (MethodSnippet) si;
String qpt;
AnalyzeTask at = state.taskFactory.new AnalyzeTask(msi.outerWrap());
String rawSig = new TreeDissector(at).typeOfMethod();
private String computeQualifiedParameterTypes(AnalyzeTask at, MethodSnippet msi) {
String rawSig = TreeDissector.createBySnippet(at, msi).typeOfMethod();
String signature = expunge(rawSig);
int paren = signature.lastIndexOf(')');
if (paren < 0) {
// Uncompilable snippet, punt with user parameter types
qpt = msi.parameterTypes();
} else {
qpt = signature.substring(0, paren + 1);
}
return qpt;
// Extract the parameter type string from the method signature,
// if method did not compile use the user-supplied parameter types
return paren >= 0
? signature.substring(0, paren + 1)
: msi.parameterTypes();
}
SnippetEvent event(String value, Exception exception) {

View File

@ -91,4 +91,14 @@ class Util {
static <T> Stream<T> stream(Iterable<T> iterable) {
return StreamSupport.stream(iterable.spliterator(), false);
}
static class Pair<T, U> {
final T first;
final U second;
Pair(T first, U second) {
this.first = first;
this.second = second;
}
}
}

View File

@ -23,6 +23,7 @@
/*
* @test
* @bug 8145239
* @summary Tests for EvaluationState.classes
* @build KullaTesting TestingInputStream ExpectedDiagnostic
* @run testng ClassesTest
@ -174,6 +175,27 @@ public class ClassesTest extends KullaTesting {
assertActiveKeys();
}
public void classesRedeclaration3() {
Snippet a = classKey(assertEval("class A { }"));
assertClasses(clazz(KullaTesting.ClassType.CLASS, "A"));
assertActiveKeys();
Snippet test1 = methodKey(assertEval("A test() { return null; }"));
Snippet test2 = methodKey(assertEval("void test(A a) { }"));
Snippet test3 = methodKey(assertEval("void test(int n) {A a;}"));
assertActiveKeys();
assertEval("interface A { }",
ste(MAIN_SNIPPET, VALID, VALID, true, null),
ste(test1, VALID, VALID, true, MAIN_SNIPPET),
ste(test2, VALID, VALID, true, MAIN_SNIPPET),
ste(test3, VALID, VALID, false, MAIN_SNIPPET),
ste(a, VALID, OVERWRITTEN, false, MAIN_SNIPPET));
assertClasses(clazz(KullaTesting.ClassType.INTERFACE, "A"));
assertMethods(method("()A", "test"), method("(A)void", "test"), method("(int)void", "test"));
assertActiveKeys();
}
public void classesCyclic1() {
Snippet b = classKey(assertEval("class B extends A { }",
added(RECOVERABLE_NOT_DEFINED)));