8189778: Jshell crash on tab for StringBuilder.append(

Fixing handling of {@inheritDoc} in JShell's documentation.

Reviewed-by: jjg, ksrini
This commit is contained in:
Jan Lahoda 2017-12-11 18:33:53 +01:00
parent f9431b7d26
commit 390de69601
2 changed files with 299 additions and 69 deletions

View File

@ -77,6 +77,7 @@ import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.DocSourcePositions;
import com.sun.source.util.DocTreePath;
import com.sun.source.util.DocTreeScanner;
import com.sun.source.util.DocTrees;
@ -194,6 +195,8 @@ public abstract class JavadocHelper implements AutoCloseable {
String docComment = trees.getDocComment(el);
if (docComment == null && element.getKind() == ElementKind.METHOD) {
//if a method does not have a javadoc,
//try to use javadoc from the methods overridden by this method:
ExecutableElement executableElement = (ExecutableElement) element;
Iterable<Element> superTypes =
() -> superTypeForInheritDoc(task, element.getEnclosingElement()).iterator();
@ -215,27 +218,65 @@ public abstract class JavadocHelper implements AutoCloseable {
}
}
if (docComment == null)
return null;
Pair<DocCommentTree, Integer> parsed = parseDocComment(task, docComment);
DocCommentTree docCommentTree = parsed.fst;
int offset = parsed.snd;
IOException[] exception = new IOException[1];
Map<int[], String> replace = new TreeMap<>((span1, span2) -> span2[0] - span1[0]);
Comparator<int[]> spanComp =
(span1, span2) -> span1[0] != span2[0] ? span2[0] - span1[0]
: span2[1] - span1[0];
//spans in the docComment that should be replaced with the given Strings:
Map<int[], List<String>> replace = new TreeMap<>(spanComp);
DocSourcePositions sp = trees.getSourcePositions();
//fill in missing elements and resolve {@inheritDoc}
//if an element is (silently) missing in the javadoc, a synthetic {@inheritDoc}
//is created for it.
new DocTreeScanner<Void, Void>() {
/* enclosing doctree that may contain {@inheritDoc} (explicit or synthetic)*/
private Stack<DocTree> interestingParent = new Stack<>();
/* current top-level DocCommentTree*/
private DocCommentTree dcTree;
private JavacTask inheritedJavacTask;
private TreePath inheritedTreePath;
/* javadoc from a super method from which we may copy elements.*/
private String inherited;
/* JavacTask from which inherited originates.*/
private JavacTask inheritedJavacTask;
/* TreePath to the super method from which inherited originates.*/
private TreePath inheritedTreePath;
/* Synthetic trees that contain {@inheritDoc} and
* texts which which they should be replaced.*/
private Map<DocTree, String> syntheticTrees = new IdentityHashMap<>();
private long lastPos = 0;
/* Position on which the synthetic trees should be inserted.*/
private long insertPos = offset;
@Override @DefinedBy(Api.COMPILER_TREE)
public Void visitDocComment(DocCommentTree node, Void p) {
dcTree = node;
interestingParent.push(node);
try {
scan(node.getFirstSentence(), p);
scan(node.getBody(), p);
if (node.getFullBody().isEmpty()) {
//there is no body in the javadoc, add synthetic {@inheritDoc}, which
//will be automatically filled in visitInheritDoc:
DocCommentTree dc = parseDocComment(task, "{@inheritDoc}").fst;
syntheticTrees.put(dc, "*\n");
interestingParent.push(dc);
boolean prevInSynthetic = inSynthetic;
try {
inSynthetic = true;
scan(dc.getFirstSentence(), p);
scan(dc.getBody(), p);
} finally {
inSynthetic = prevInSynthetic;
interestingParent.pop();
}
} else {
scan(node.getFirstSentence(), p);
scan(node.getBody(), p);
}
//add missing @param, @throws and @return, augmented with {@inheritDoc}
//which will be resolved in visitInheritDoc:
List<DocTree> augmentedBlockTags = new ArrayList<>(node.getBlockTags());
if (element.getKind() == ElementKind.METHOD) {
ExecutableElement executableElement = (ExecutableElement) element;
@ -269,19 +310,19 @@ public abstract class JavadocHelper implements AutoCloseable {
for (String missingParam : missingParams) {
DocTree syntheticTag = parseBlockTag(task, "@param " + missingParam + " {@inheritDoc}");
syntheticTrees.put(syntheticTag, "@param " + missingParam + " ");
syntheticTrees.put(syntheticTag, "@param " + missingParam + " *\n");
insertTag(augmentedBlockTags, syntheticTag, parameters, throwsList);
}
for (String missingThrow : missingThrows) {
DocTree syntheticTag = parseBlockTag(task, "@throws " + missingThrow + " {@inheritDoc}");
syntheticTrees.put(syntheticTag, "@throws " + missingThrow + " ");
syntheticTrees.put(syntheticTag, "@throws " + missingThrow + " *\n");
insertTag(augmentedBlockTags, syntheticTag, parameters, throwsList);
}
if (!hasReturn) {
DocTree syntheticTag = parseBlockTag(task, "@return {@inheritDoc}");
syntheticTrees.put(syntheticTag, "@return ");
syntheticTrees.put(syntheticTag, "@return *\n");
insertTag(augmentedBlockTags, syntheticTag, parameters, throwsList);
}
}
@ -320,26 +361,32 @@ public abstract class JavadocHelper implements AutoCloseable {
}
@Override @DefinedBy(Api.COMPILER_TREE)
public Void visitInheritDoc(InheritDocTree node, Void p) {
//replace (schedule replacement into the replace map)
//{@inheritDoc} with the corresponding text from an overridden method
//first, fill in inherited, inheritedJavacTask and inheritedTreePath if not
//done yet:
if (inherited == null) {
try {
if (element.getKind() == ElementKind.METHOD) {
ExecutableElement executableElement = (ExecutableElement) element;
Iterable<Element> superTypes = () -> superTypeForInheritDoc(task, element.getEnclosingElement()).iterator();
OUTER: for (Element sup : superTypes) {
for (ExecutableElement supMethod : ElementFilter.methodsIn(sup.getEnclosedElements())) {
if (task.getElements().overrides(executableElement, supMethod, (TypeElement) executableElement.getEnclosingElement())) {
Pair<JavacTask, TreePath> source = getSourceElement(task, supMethod);
Iterable<ExecutableElement> superMethods =
() -> superMethodsForInheritDoc(task, executableElement).
iterator();
for (Element supMethod : superMethods) {
Pair<JavacTask, TreePath> source =
getSourceElement(task, supMethod);
if (source != null) {
String overriddenComment = getResolvedDocComment(source.fst, source.snd);
if (source != null) {
String overriddenComment =
getResolvedDocComment(source.fst,
source.snd);
if (overriddenComment != null) {
inheritedJavacTask = source.fst;
inheritedTreePath = source.snd;
inherited = overriddenComment;
break OUTER;
}
}
if (overriddenComment != null) {
inheritedJavacTask = source.fst;
inheritedTreePath = source.snd;
inherited = overriddenComment;
break;
}
}
}
@ -357,6 +404,8 @@ public abstract class JavadocHelper implements AutoCloseable {
DocCommentTree inheritedDocTree = parsed.fst;
int offset = parsed.snd;
List<List<? extends DocTree>> inheritedText = new ArrayList<>();
//find the corresponding piece in the inherited javadoc
//(interesting parent keeps the enclosing tree):
DocTree parent = interestingParent.peek();
switch (parent.getKind()) {
case DOC_COMMENT:
@ -401,18 +450,29 @@ public abstract class JavadocHelper implements AutoCloseable {
long end = Long.MIN_VALUE;
for (DocTree t : inheritedText.get(0)) {
start = Math.min(start, trees.getSourcePositions().getStartPosition(null, inheritedDocTree, t) - offset);
end = Math.max(end, trees.getSourcePositions().getEndPosition(null, inheritedDocTree, t) - offset);
start = Math.min(start,
sp.getStartPosition(null, inheritedDocTree, t) - offset);
end = Math.max(end,
sp.getEndPosition(null, inheritedDocTree, t) - offset);
}
String text = inherited.substring((int) start, (int) end);
String text = end >= 0 ? inherited.substring((int) start, (int) end) : "";
if (syntheticTrees.containsKey(parent)) {
replace.put(new int[] {(int) lastPos + 1, (int) lastPos}, "\n" + syntheticTrees.get(parent) + text);
//if the {@inheritDoc} is inside a synthetic tree, don't delete anything,
//but insert the required text
//(insertPos is the position at which new stuff should be added):
int[] span = new int[] {(int) insertPos, (int) insertPos};
replace.computeIfAbsent(span, s -> new ArrayList<>())
.add(syntheticTrees.get(parent).replace("*", text));
} else {
long inheritedStart = trees.getSourcePositions().getStartPosition(null, dcTree, node);
long inheritedEnd = trees.getSourcePositions().getEndPosition(null, dcTree, node);
//replace the {@inheritDoc} with the full text from
//the overridden method:
long inheritedStart = sp.getStartPosition(null, dcTree, node);
long inheritedEnd = sp.getEndPosition(null, dcTree, node);
int[] span = new int[] {(int) inheritedStart, (int) inheritedEnd};
replace.put(new int[] {(int) inheritedStart, (int) inheritedEnd}, text);
replace.computeIfAbsent(span, s -> new ArrayList<>())
.add(text);
}
}
return super.visitInheritDoc(node, p);
@ -428,13 +488,31 @@ public abstract class JavadocHelper implements AutoCloseable {
inSynthetic |= syntheticTrees.containsKey(tree);
return super.scan(tree, p);
} finally {
if (!inSynthetic) {
lastPos = trees.getSourcePositions().getEndPosition(null, dcTree, tree);
if (!inSynthetic && tree != null) {
//for nonsynthetic trees, preserve the ending position as the future
//insertPos (as future missing elements should be inserted behind
//this tree)
//if there is a newline immediately behind this tree, insert behind
//the newline:
long endPos = sp.getEndPosition(null, dcTree, tree);
if (endPos >= 0) {
if (endPos - offset + 1 < docComment.length() &&
docComment.charAt((int) (endPos - offset + 1)) == '\n') {
endPos++;
}
if (endPos - offset < docComment.length()) {
insertPos = endPos + 1;
} else {
insertPos = endPos;
}
}
}
inSynthetic = prevInSynthetic;
}
}
/* Insert a synthetic tag (toInsert) into the list of tags at
* an appropriate position.*/
private void insertTag(List<DocTree> tags, DocTree toInsert, List<String> parameters, List<String> throwsTypes) {
Comparator<DocTree> comp = (tag1, tag2) -> {
if (tag1.getKind() == tag2.getKind()) {
@ -479,16 +557,30 @@ public abstract class JavadocHelper implements AutoCloseable {
if (replace.isEmpty())
return docComment;
//do actually replace {@inheritDoc} with the new text (as scheduled by the visitor
//above):
StringBuilder replacedInheritDoc = new StringBuilder(docComment);
for (Entry<int[], String> e : replace.entrySet()) {
replacedInheritDoc.delete(e.getKey()[0] - offset, e.getKey()[1] - offset + 1);
replacedInheritDoc.insert(e.getKey()[0] - offset, e.getValue());
for (Entry<int[], List<String>> e : replace.entrySet()) {
replacedInheritDoc.delete(e.getKey()[0] - offset, e.getKey()[1] - offset);
replacedInheritDoc.insert(e.getKey()[0] - offset,
e.getValue().stream().collect(Collectors.joining("")));
}
return replacedInheritDoc.toString();
}
/* Find methods from which the given method may inherit javadoc, in the proper order.*/
private Stream<ExecutableElement> superMethodsForInheritDoc(JavacTask task,
ExecutableElement method) {
TypeElement type = (TypeElement) method.getEnclosingElement();
return this.superTypeForInheritDoc(task, type)
.flatMap(sup -> ElementFilter.methodsIn(sup.getEnclosedElements()).stream())
.filter(supMethod -> task.getElements().overrides(method, supMethod, type));
}
/* Find types from which methods in type may inherit javadoc, in the proper order.*/
private Stream<Element> superTypeForInheritDoc(JavacTask task, Element type) {
TypeElement clazz = (TypeElement) type;
Stream<Element> result = interfaces(clazz);
@ -529,7 +621,7 @@ public abstract class JavadocHelper implements AutoCloseable {
};
DocCommentTree tree = trees.getDocCommentTree(fo);
int offset = (int) trees.getSourcePositions().getStartPosition(null, tree, tree);
offset += "<body>".length() + 1;
offset += "<body>".length();
return Pair.of(tree, offset);
} catch (URISyntaxException ex) {
throw new IllegalStateException(ex);

View File

@ -23,28 +23,37 @@
/*
* @test
* @bug 8131019 8190552
* @bug 8131019 8189778 8190552
* @summary Test JavadocHelper
* @library /tools/lib
* @modules jdk.compiler/com.sun.tools.javac.api
* jdk.compiler/com.sun.tools.javac.main
* jdk.compiler/jdk.internal.shellsupport.doc
* @build toolbox.ToolBox toolbox.JarTask toolbox.JavacTask
* @run testng JavadocHelperTest
* @run testng/timeout=900/othervm JavadocHelperTest
*/
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.DirectoryStream;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import javax.lang.model.element.Element;
import javax.lang.model.element.ModuleElement;
import javax.lang.model.element.ModuleElement.ExportsDirective;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic.Kind;
import javax.tools.DiagnosticListener;
@ -70,15 +79,15 @@ public class JavadocHelperTest {
"Top level. ");
doTestJavadoc("",
t -> getFirstMethod(t, "test.Super"),
" javadoc1A\n" +
" javadoc1\n" +
"\n" +
" @param p1 param1A\n" +
" @param p2 param2A\n" +
" @param p3 param3A\n" +
" @throws IllegalStateException exc1A\n" +
" @throws IllegalArgumentException exc2A\n" +
" @throws IllegalAccessException exc3A\n" +
" @return valueA\n");
" @param p1 param1\n" +
" @param p2 param2\n" +
" @param p3 param3\n" +
" @throws IllegalStateException exc1\n" +
" @throws IllegalArgumentException exc2\n" +
" @throws IllegalAccessException exc3\n" +
" @return value\n");
}
private Element getFirstMethod(JavacTask task, String typeName) {
@ -90,15 +99,15 @@ public class JavadocHelperTest {
public void testInheritNoJavadoc() throws Exception {
doTestJavadoc("",
getSubTest,
" javadoc1A\n" +
" javadoc1\n" +
"\n" +
" @param p1 param1A\n" +
" @param p2 param2A\n" +
" @param p3 param3A\n" +
" @throws IllegalStateException exc1A\n" +
" @throws IllegalArgumentException exc2A\n" +
" @throws IllegalAccessException exc3A\n" +
" @return valueA\n");
" @param p1 param1\n" +
" @param p2 param2\n" +
" @param p3 param3\n" +
" @throws IllegalStateException exc1\n" +
" @throws IllegalArgumentException exc2\n" +
" @throws IllegalAccessException exc3\n" +
" @return value\n");
}
public void testInheritFull() throws Exception {
@ -140,7 +149,7 @@ public class JavadocHelperTest {
" Prefix javadoc1 suffix.\n" +
"\n" +
" @param p1 prefix param1 suffix\n" +
"@param p2 param2\n" +
"@param p2 param2\n" +
" @param p3 prefix param3 suffix\n" +
" @throws IllegalStateException prefix exc1 suffix\n" +
" @throws IllegalArgumentException prefix exc2 suffix\n" +
@ -161,8 +170,8 @@ public class JavadocHelperTest {
" */\n",
getSubTest,
" Prefix javadoc1 suffix.\n" +
"@param p1 param1\n" +
"\n" +
"@param p1 param1\n" +
" @param p2 prefix param2 suffix\n" +
" @param p3 prefix param3 suffix\n" +
" @throws IllegalStateException prefix exc1 suffix\n" +
@ -189,7 +198,7 @@ public class JavadocHelperTest {
" @param p2 prefix param2 suffix\n" +
" @param p3 prefix param3 suffix\n" +
" @throws IllegalStateException prefix exc1 suffix\n" +
"@throws java.lang.IllegalArgumentException exc2\n" +
"@throws java.lang.IllegalArgumentException exc2\n" +
" @throws IllegalAccessException prefix exc3 suffix\n" +
" @return prefix value suffix\n");
}
@ -214,11 +223,101 @@ public class JavadocHelperTest {
" @throws IllegalStateException prefix exc1 suffix\n" +
" @throws IllegalArgumentException prefix exc2 suffix\n" +
" @throws IllegalAccessException prefix exc3 suffix\n" +
"@return value\n");
"@return value\n");
}
public void testInheritAllButOne() throws Exception {
doTestJavadoc(" /**\n" +
" * @throws IllegalArgumentException {@inheritDoc}\n" +
" */\n",
getSubTest,
"javadoc1\n" +
"@param p1 param1\n" +
"@param p2 param2\n" +
"@param p3 param3\n" +
"@throws java.lang.IllegalStateException exc1\n" +
" @throws IllegalArgumentException exc2\n" +
"@throws java.lang.IllegalAccessException exc3\n" +
"@return value\n");
}
public void testInheritEmpty() throws Exception {
doTestJavadoc(" /**\n" +
" */\n",
" /**@param p1\n" +
" * @param p2\n" +
" * @param p3\n" +
" * @throws IllegalStateException\n" +
" * @throws IllegalArgumentException\n" +
" * @throws IllegalAccessException\n" +
" * @return\n" +
" */\n",
getSubTest,
"\n" +
"@param p1 \n" +
"@param p2 \n" +
"@param p3 \n" +
"@throws java.lang.IllegalStateException \n" +
"@throws java.lang.IllegalArgumentException \n" +
"@throws java.lang.IllegalAccessException \n" +
"@return \n");
}
public void testEmptyValue() throws Exception {
doTestJavadoc(" /**\n" +
" */\n",
" /**@param p1 {@value}\n" +
" * @param p2\n" +
" * @param p3\n" +
" * @throws IllegalStateException\n" +
" * @throws IllegalArgumentException\n" +
" * @throws IllegalAccessException\n" +
" * @return\n" +
" */\n",
getSubTest,
"\n" +
"@param p1 {@value}\n" +
"@param p2 \n" +
"@param p3 \n" +
"@throws java.lang.IllegalStateException \n" +
"@throws java.lang.IllegalArgumentException \n" +
"@throws java.lang.IllegalAccessException \n" +
"@return \n");
}
public void testShortComment() throws Exception {
doTestJavadoc(" /**Test.*/\n",
getSubTest,
"Test." +
"@param p1 param1\n" +
"@param p2 param2\n" +
"@param p3 param3\n" +
"@throws java.lang.IllegalStateException exc1\n" +
"@throws java.lang.IllegalArgumentException exc2\n" +
"@throws java.lang.IllegalAccessException exc3\n" +
"@return value\n");
}
private void doTestJavadoc(String origJavadoc, Function<JavacTask, Element> getElement, String expectedJavadoc) throws Exception {
doTestJavadoc(origJavadoc,
" /**\n" +
" * javadoc1\n" +
" *\n" +
" * @param p1 param1\n" +
" * @param p2 param2\n" +
" * @param p3 param3\n" +
" * @throws IllegalStateException exc1\n" +
" * @throws IllegalArgumentException exc2\n" +
" * @throws IllegalAccessException exc3\n" +
" * @return value\n" +
" */\n",
getElement, expectedJavadoc);
}
private void doTestJavadoc(String origJavadoc,
String superJavadoc,
Function<JavacTask, Element> getElement,
String expectedJavadoc) throws Exception {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
String subClass =
"package test;\n" +
@ -231,17 +330,7 @@ public class JavadocHelperTest {
"/**Top level." +
" */\n" +
"public class Super {\n" +
" /**\n" +
" * javadoc1A\n" +
" *\n" +
" * @param p1 param1A\n" +
" * @param p2 param2A\n" +
" * @param p3 param3A\n" +
" * @throws IllegalStateException exc1A\n" +
" * @throws IllegalArgumentException exc2A\n" +
" * @throws IllegalAccessException exc3A\n" +
" * @return valueA\n" +
" */\n" +
superJavadoc +
" public String test(int p1, int p2, int p3) throws IllegalStateException, IllegalArgumentException, IllegalAccessException { return null;} \n" +
"}\n";
@ -293,4 +382,53 @@ public class JavadocHelperTest {
}
}
public void testAllDocs() throws IOException {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticListener<? super JavaFileObject> noErrors = d -> {
if (d.getKind() == Kind.ERROR) {
throw new AssertionError(d.getMessage(null));
}
};
List<Path> sources = new ArrayList<>();
Path home = Paths.get(System.getProperty("java.home"));
Path srcZip = home.resolve("lib").resolve("src.zip");
if (Files.isReadable(srcZip)) {
URI uri = URI.create("jar:" + srcZip.toUri());
try (FileSystem zipFO = FileSystems.newFileSystem(uri, Collections.emptyMap())) {
Path root = zipFO.getRootDirectories().iterator().next();
//modular format:
try (DirectoryStream<Path> ds = Files.newDirectoryStream(root)) {
for (Path p : ds) {
if (Files.isDirectory(p)) {
sources.add(p);
}
}
}
try (StandardJavaFileManager fm =
compiler.getStandardFileManager(null, null, null)) {
JavacTask task =
(JavacTask) compiler.getTask(null, fm, noErrors, null, null, null);
task.getElements().getTypeElement("java.lang.Object");
for (ModuleElement me : task.getElements().getAllModuleElements()) {
List<ExportsDirective> exports =
ElementFilter.exportsIn(me.getDirectives());
for (ExportsDirective ed : exports) {
try (JavadocHelper helper = JavadocHelper.create(task, sources)) {
List<? extends Element> content =
ed.getPackage().getEnclosedElements();
for (TypeElement clazz : ElementFilter.typesIn(content)) {
for (Element el : clazz.getEnclosedElements()) {
helper.getResolvedDocComment(el);
}
}
}
}
}
}
}
}
}
}