--- a/langtools/src/jdk.compiler/share/classes/com/sun/source/util/DocTreePath.java Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/source/util/DocTreePath.java Tue Jul 12 14:41:14 2016 -0700
@@ -98,7 +98,7 @@
* @param t the DocCommentTree to create the path for.
*/
public DocTreePath(TreePath treePath, DocCommentTree t) {
- this.treePath = Objects.requireNonNull(treePath);
+ this.treePath = treePath;
this.docComment = Objects.requireNonNull(t);
this.parent = null;
this.leaf = t;
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/doclint/Checker.java Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/doclint/Checker.java Tue Jul 12 14:41:14 2016 -0700
@@ -423,7 +423,16 @@
break;
case OTHER:
- env.messages.error(HTML, tree, "dc.tag.not.allowed", treeName);
+ switch (t) {
+ case SCRIPT:
+ // <script> may or may not be allowed, depending on --allow-script-in-comments
+ // but we allow it here, and rely on a separate scanner to detect all uses
+ // of JavaScript, including <script> tags, and use in attributes, etc.
+ break;
+
+ default:
+ env.messages.error(HTML, tree, "dc.tag.not.allowed", treeName);
+ }
return;
}
@@ -552,15 +561,19 @@
if (!first)
env.messages.error(HTML, tree, "dc.attr.repeated", name);
}
- AttrKind k = currTag.getAttrKind(name);
- switch (env.htmlVersion) {
- case HTML4:
- validateHtml4Attrs(tree, name, k);
- break;
+ // for now, doclint allows all attribute names beginning with "on" as event handler names,
+ // without checking the validity or applicability of the name
+ if (!name.toString().startsWith("on")) {
+ AttrKind k = currTag.getAttrKind(name);
+ switch (env.htmlVersion) {
+ case HTML4:
+ validateHtml4Attrs(tree, name, k);
+ break;
- case HTML5:
- validateHtml5Attrs(tree, name, k);
- break;
+ case HTML5:
+ validateHtml5Attrs(tree, name, k);
+ break;
+ }
}
if (attr != null) {
@@ -722,6 +735,9 @@
}
private void checkURI(AttributeTree tree, String uri) {
+ // allow URIs beginning with javascript:, which would otherwise be rejected by the URI API.
+ if (uri.startsWith("javascript:"))
+ return;
try {
URI u = new URI(uri);
} catch (URISyntaxException e) {
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/doclint/HtmlTag.java Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/doclint/HtmlTag.java Tue Jul 12 14:41:14 2016 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2016, 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
@@ -287,7 +287,8 @@
SAMP(BlockType.INLINE, EndKind.REQUIRED,
EnumSet.of(Flag.EXPECT_CONTENT, Flag.NO_NEST)),
- SCRIPT(BlockType.OTHER, EndKind.REQUIRED),
+ SCRIPT(BlockType.OTHER, EndKind.REQUIRED,
+ attrs(AttrKind.ALL, SRC)),
SECTION(HtmlVersion.HTML5, BlockType.BLOCK, EndKind.REQUIRED,
EnumSet.of(Flag.ACCEPTS_BLOCK, Flag.ACCEPTS_INLINE)),
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/DocCommentParser.java Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/DocCommentParser.java Tue Jul 12 14:41:14 2016 -0700
@@ -30,7 +30,6 @@
import java.util.Map;
import com.sun.source.doctree.AttributeTree.ValueKind;
-import com.sun.source.doctree.DocTree;
import com.sun.tools.javac.parser.DocCommentParser.TagParser.Kind;
import com.sun.tools.javac.parser.Tokens.Comment;
import com.sun.tools.javac.parser.Tokens.TokenKind;
--- a/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java Tue Jul 12 14:41:14 2016 -0700
@@ -588,7 +588,7 @@
/**
* Ident = IDENTIFIER
*/
- protected Name ident() {
+ public Name ident() {
return ident(false);
}
--- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/formats/html/ConfigurationImpl.java Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/formats/html/ConfigurationImpl.java Tue Jul 12 14:41:14 2016 -0700
@@ -40,6 +40,7 @@
import com.sun.tools.javac.file.JavacFileManager;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.StringUtils;
+import com.sun.tools.javadoc.main.JavaScriptScanner;
import com.sun.tools.javadoc.main.RootDocImpl;
/**
@@ -189,6 +190,11 @@
public Set<String> doclintOpts = new LinkedHashSet<>();
/**
+ * Whether or not to check for JavaScript in doc comments.
+ */
+ private boolean allowScriptInComments;
+
+ /**
* Unique Resource Handler for this package.
*/
public final MessageRetriever standardmessage;
@@ -309,8 +315,11 @@
doclintOpts.add(DocLint.XMSGS_CUSTOM_PREFIX + opt.substring(opt.indexOf(":") + 1));
} else if (opt.startsWith("-xdoclint/package:")) {
doclintOpts.add(DocLint.XCHECK_PACKAGE + opt.substring(opt.indexOf(":") + 1));
+ } else if (opt.equals("--allow-script-in-comments")) {
+ allowScriptInComments = true;
}
}
+
if (root.specifiedClasses().length > 0) {
Map<String,PackageDoc> map = new HashMap<>();
PackageDoc pd;
@@ -322,15 +331,37 @@
}
}
}
+
setCreateOverview();
setTopFile(root);
if (root instanceof RootDocImpl) {
((RootDocImpl) root).initDocLint(doclintOpts, tagletManager.getCustomTagNames(),
StringUtils.toLowerCase(htmlVersion.name()));
+ JavaScriptScanner jss = ((RootDocImpl) root).initJavaScriptScanner(isAllowScriptInComments());
+ if (jss != null) {
+ // In a more object-oriented world, this would be done by methods on the Option objects.
+ // Note that -windowtitle silently removes any and all HTML elements, and so does not need
+ // to be handled here.
+ checkJavaScript(jss, "-header", header);
+ checkJavaScript(jss, "-footer", footer);
+ checkJavaScript(jss, "-top", top);
+ checkJavaScript(jss, "-bottom", bottom);
+ checkJavaScript(jss, "-doctitle", doctitle);
+ checkJavaScript(jss, "-packagesheader", packagesheader);
+ }
}
}
+ private void checkJavaScript(JavaScriptScanner jss, final String opt, String value) {
+ jss.parse(value, new JavaScriptScanner.Reporter() {
+ public void report() {
+ root.printError(getText("doclet.JavaScript_in_option", opt));
+ throw new FatalError();
+ }
+ });
+ }
+
/**
* Returns the "length" of a given option. If an option takes no
* arguments, its length is one. If it takes one argument, it's
@@ -366,7 +397,8 @@
option.equals("-html5") ||
option.equals("-xdoclint") ||
option.startsWith("-xdoclint:") ||
- option.startsWith("-xdoclint/package:")) {
+ option.startsWith("-xdoclint/package:") ||
+ option.startsWith("--allow-script-in-comments")) {
return 1;
} else if (option.equals("-help")) {
// Uugh: first, this should not be hidden inside optionLength,
@@ -666,4 +698,13 @@
}
tagSearchIndexKeys = tagSearchIndexMap.keySet();
}
+
+ /**
+ * Returns whether or not to allow JavaScript in comments.
+ * Default is off; can be set true from a command line option.
+ * @return the allowScriptInComments
+ */
+ public boolean isAllowScriptInComments() {
+ return allowScriptInComments;
+ }
}
--- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/formats/html/HtmlDoclet.java Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/formats/html/HtmlDoclet.java Tue Jul 12 14:41:14 2016 -0700
@@ -237,6 +237,8 @@
}
} catch (IOException e) {
throw new DocletAbortException(e);
+ } catch (FatalError fe) {
+ throw fe;
} catch (DocletAbortException de) {
de.printStackTrace();
throw de;
--- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/formats/html/markup/HtmlWriter.java Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/formats/html/markup/HtmlWriter.java Tue Jul 12 14:41:14 2016 -0700
@@ -148,7 +148,9 @@
public final Content descfrmInterfaceLabel;
- private final Writer writer;
+ private final DocFile file;
+
+ private Writer writer;
protected Content script;
@@ -164,7 +166,7 @@
*/
public HtmlWriter(Configuration configuration, DocPath path)
throws IOException, UnsupportedEncodingException {
- writer = DocFile.createFileForOutput(configuration, path).openWriter();
+ file = DocFile.createFileForOutput(configuration, path);
this.configuration = configuration;
this.memberDetailsListPrinted = false;
packageTableHeader = new String[] {
@@ -214,6 +216,7 @@
}
public void write(Content c) throws IOException {
+ writer = file.openWriter();
c.write(writer, true);
}
--- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/AbstractDoclet.java Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/AbstractDoclet.java Tue Jul 12 14:41:14 2016 -0700
@@ -90,6 +90,8 @@
} catch (Configuration.Fault f) {
root.printError(f.getMessage());
return false;
+ } catch (FatalError fe) {
+ return false;
} catch (DocletAbortException e) {
e.printStackTrace();
Throwable cause = e.getCause();
--- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/builders/AbstractBuilder.java Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/builders/AbstractBuilder.java Tue Jul 12 14:41:14 2016 -0700
@@ -145,7 +145,14 @@
configuration.root.printError("Unknown element: " + component);
throw new DocletAbortException(e);
} catch (InvocationTargetException e) {
- throw new DocletAbortException(e.getCause());
+ Throwable cause = e.getCause();
+ if (cause instanceof FatalError) {
+ throw (FatalError) cause;
+ } else if (cause instanceof DocletAbortException) {
+ throw (DocletAbortException) cause;
+ } else {
+ throw new DocletAbortException(cause);
+ }
} catch (Exception e) {
e.printStackTrace();
configuration.root.printError("Exception " +
--- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/resources/doclets.properties Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/resources/doclets.properties Tue Jul 12 14:41:14 2016 -0700
@@ -29,6 +29,8 @@
doclet.Building_Tree=Building tree for all the packages and classes...
doclet.Building_Index=Building index for all the packages and classes...
doclet.Building_Index_For_All_Classes=Building index for all classes...
+doclet.JavaScript_in_option=Argument for {0} contains JavaScript.\n\
+Use --allow-script-in-comments to allow use of JavaScript.
doclet.sourcetab_warning=The argument for -sourcetab must be an integer greater than 0.
doclet.Packages=Packages
doclet.Other_Packages=Other Packages
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/doclets/internal/toolkit/util/FatalError.java Tue Jul 12 14:41:14 2016 -0700
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.tools.doclets.internal.toolkit.util;
+
+/**
+ * <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>
+ */
+@Deprecated
+public class FatalError extends Error {
+ private static final long serialVersionUID = -9131058909576418984L;
+
+ public FatalError() { }
+}
--- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/main/DocEnv.java Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/main/DocEnv.java Tue Jul 12 14:41:14 2016 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2016, 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
@@ -97,7 +97,7 @@
final Enter enter;
/** The name table. */
- private Names names;
+ private final Names names;
/** The encoding name. */
private String encoding;
@@ -120,6 +120,7 @@
JavaFileManager fileManager;
Context context;
DocLint doclint;
+ JavaScriptScanner javaScriptScanner;
WeakHashMap<JCTree, TreePath> treePaths = new WeakHashMap<>();
@@ -858,6 +859,15 @@
doclint.init(t, doclintOpts.toArray(new String[doclintOpts.size()]), false);
}
+ JavaScriptScanner initJavaScriptScanner(boolean allowScriptInComments) {
+ if (allowScriptInComments) {
+ javaScriptScanner = null;
+ } else {
+ javaScriptScanner = new JavaScriptScanner();
+ }
+ return javaScriptScanner;
+ }
+
boolean showTagMessages() {
return (doclint == null);
}
--- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/main/DocImpl.java Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/main/DocImpl.java Tue Jul 12 14:41:14 2016 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2016, 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
@@ -36,6 +36,8 @@
import com.sun.javadoc.*;
import com.sun.source.util.TreePath;
+import com.sun.tools.doclets.internal.toolkit.util.DocletAbortException;
+import com.sun.tools.doclets.internal.toolkit.util.FatalError;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
import com.sun.tools.javac.util.Position;
@@ -128,6 +130,15 @@
Comment comment() {
if (comment == null) {
String d = documentation();
+ if (env.javaScriptScanner != null) {
+ env.javaScriptScanner.parse(d, new JavaScriptScanner.Reporter() {
+ @Override
+ public void report() {
+ env.error(DocImpl.this, "javadoc.JavaScript_in_comment");
+ throw new FatalError();
+ }
+ });
+ }
if (env.doclint != null
&& treePath != null
&& env.shouldCheck(treePath.getCompilationUnit())
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/main/JavaScriptScanner.java Tue Jul 12 14:41:14 2016 -0700
@@ -0,0 +1,1103 @@
+/*
+ * Copyright (c) 2012,2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.tools.javadoc.main;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import com.sun.tools.javadoc.main.JavaScriptScanner.TagParser.Kind;
+
+import static com.sun.tools.javac.util.LayoutCharacters.EOI;
+
+/**
+ * Parser to detect use of JavaScript in documentation comments.
+ */
+@Deprecated
+public class JavaScriptScanner {
+ public static interface Reporter {
+ void report();
+ }
+
+ static class ParseException extends Exception {
+ private static final long serialVersionUID = 0;
+ ParseException(String key) {
+ super(key);
+ }
+ }
+
+ private Reporter reporter;
+
+ /** The input buffer, index of most recent character read,
+ * index of one past last character in buffer.
+ */
+ protected char[] buf;
+ protected int bp;
+ protected int buflen;
+
+ /** The current character.
+ */
+ protected char ch;
+
+ private boolean newline = true;
+
+ Map<String, TagParser> tagParsers;
+ Set<String> eventAttrs;
+ Set<String> uriAttrs;
+
+ public JavaScriptScanner() {
+ initTagParsers();
+ initEventAttrs();
+ initURIAttrs();
+ }
+
+ public void parse(String comment, Reporter r) {
+ reporter = r;
+ String c = comment;
+ buf = new char[c.length() + 1];
+ c.getChars(0, c.length(), buf, 0);
+ buf[buf.length - 1] = EOI;
+ buflen = buf.length - 1;
+ bp = -1;
+ newline = true;
+ nextChar();
+
+ blockContent();
+ blockTags();
+ }
+
+ private void checkHtmlTag(String tag) {
+ if (tag.equalsIgnoreCase("script")) {
+ reporter.report();
+ }
+ }
+
+ private void checkHtmlAttr(String name, String value) {
+ String n = name.toLowerCase(Locale.ENGLISH);
+ if (eventAttrs.contains(n)
+ || uriAttrs.contains(n)
+ && value != null && value.toLowerCase(Locale.ENGLISH).trim().startsWith("javascript:")) {
+ reporter.report();
+ }
+ }
+
+ void nextChar() {
+ ch = buf[bp < buflen ? ++bp : buflen];
+ switch (ch) {
+ case '\f': case '\n': case '\r':
+ newline = true;
+ }
+ }
+
+ /**
+ * Read block content, consisting of text, html and inline tags.
+ * Terminated by the end of input, or the beginning of the next block tag:
+ * i.e. @ as the first non-whitespace character on a line.
+ */
+ @SuppressWarnings("fallthrough")
+ protected void blockContent() {
+
+ loop:
+ while (bp < buflen) {
+ switch (ch) {
+ case '\n': case '\r': case '\f':
+ newline = true;
+ // fallthrough
+
+ case ' ': case '\t':
+ nextChar();
+ break;
+
+ case '&':
+ entity(null);
+ break;
+
+ case '<':
+ html();
+ break;
+
+ case '>':
+ newline = false;
+ nextChar();
+ break;
+
+ case '{':
+ inlineTag(null);
+ break;
+
+ case '@':
+ if (newline) {
+ break loop;
+ }
+ // fallthrough
+
+ default:
+ newline = false;
+ nextChar();
+ }
+ }
+ }
+
+ /**
+ * Read a series of block tags, including their content.
+ * Standard tags parse their content appropriately.
+ * Non-standard tags are represented by {@link UnknownBlockTag}.
+ */
+ protected void blockTags() {
+ while (ch == '@')
+ blockTag();
+ }
+
+ /**
+ * Read a single block tag, including its content.
+ * Standard tags parse their content appropriately.
+ * Non-standard tags are represented by {@link UnknownBlockTag}.
+ */
+ protected void blockTag() {
+ int p = bp;
+ try {
+ nextChar();
+ if (isIdentifierStart(ch)) {
+ String name = readTagName();
+ TagParser tp = tagParsers.get(name);
+ if (tp == null) {
+ blockContent();
+ } else {
+ switch (tp.getKind()) {
+ case BLOCK:
+ tp.parse(p);
+ return;
+ case INLINE:
+ return;
+ }
+ }
+ }
+ blockContent();
+ } catch (ParseException e) {
+ blockContent();
+ }
+ }
+
+ protected void inlineTag(Void list) {
+ newline = false;
+ nextChar();
+ if (ch == '@') {
+ inlineTag();
+ }
+ }
+
+ /**
+ * Read a single inline tag, including its content.
+ * Standard tags parse their content appropriately.
+ * Non-standard tags are represented by {@link UnknownBlockTag}.
+ * Malformed tags may be returned as {@link Erroneous}.
+ */
+ protected void inlineTag() {
+ int p = bp - 1;
+ try {
+ nextChar();
+ if (isIdentifierStart(ch)) {
+ String name = readTagName();
+ TagParser tp = tagParsers.get(name);
+
+ if (tp == null) {
+ skipWhitespace();
+ inlineText(WhitespaceRetentionPolicy.REMOVE_ALL);
+ nextChar();
+ } else {
+ skipWhitespace();
+ if (tp.getKind() == TagParser.Kind.INLINE) {
+ tp.parse(p);
+ } else { // handle block tags (ex: @see) in inline content
+ inlineText(WhitespaceRetentionPolicy.REMOVE_ALL); // skip content
+ nextChar();
+ }
+ }
+ }
+ } catch (ParseException e) {
+ }
+ }
+
+ private static enum WhitespaceRetentionPolicy {
+ RETAIN_ALL,
+ REMOVE_FIRST_SPACE,
+ REMOVE_ALL
+ }
+
+ /**
+ * Read plain text content of an inline tag.
+ * Matching pairs of { } are skipped; the text is terminated by the first
+ * unmatched }. It is an error if the beginning of the next tag is detected.
+ */
+ private void inlineText(WhitespaceRetentionPolicy whitespacePolicy) throws ParseException {
+ switch (whitespacePolicy) {
+ case REMOVE_ALL:
+ skipWhitespace();
+ break;
+ case REMOVE_FIRST_SPACE:
+ if (ch == ' ')
+ nextChar();
+ break;
+ case RETAIN_ALL:
+ default:
+ // do nothing
+ break;
+
+ }
+ int pos = bp;
+ int depth = 1;
+
+ loop:
+ while (bp < buflen) {
+ switch (ch) {
+ case '\n': case '\r': case '\f':
+ newline = true;
+ break;
+
+ case ' ': case '\t':
+ break;
+
+ case '{':
+ newline = false;
+ depth++;
+ break;
+
+ case '}':
+ if (--depth == 0) {
+ return;
+ }
+ newline = false;
+ break;
+
+ case '@':
+ if (newline)
+ break loop;
+ newline = false;
+ break;
+
+ default:
+ newline = false;
+ break;
+ }
+ nextChar();
+ }
+ throw new ParseException("dc.unterminated.inline.tag");
+ }
+
+ /**
+ * Read Java class name, possibly followed by member
+ * Matching pairs of {@literal < >} are skipped. The text is terminated by the first
+ * unmatched }. It is an error if the beginning of the next tag is detected.
+ */
+ // TODO: boolean allowMember should be enum FORBID, ALLOW, REQUIRE
+ // TODO: improve quality of parse to forbid bad constructions.
+ // TODO: update to use ReferenceParser
+ @SuppressWarnings("fallthrough")
+ protected void reference(boolean allowMember) throws ParseException {
+ int pos = bp;
+ int depth = 0;
+
+ // scan to find the end of the signature, by looking for the first
+ // whitespace not enclosed in () or <>, or the end of the tag
+ loop:
+ while (bp < buflen) {
+ switch (ch) {
+ case '\n': case '\r': case '\f':
+ newline = true;
+ // fallthrough
+
+ case ' ': case '\t':
+ if (depth == 0)
+ break loop;
+ break;
+
+ case '(':
+ case '<':
+ newline = false;
+ depth++;
+ break;
+
+ case ')':
+ case '>':
+ newline = false;
+ --depth;
+ break;
+
+ case '}':
+ if (bp == pos)
+ return;
+ newline = false;
+ break loop;
+
+ case '@':
+ if (newline)
+ break loop;
+ // fallthrough
+
+ default:
+ newline = false;
+
+ }
+ nextChar();
+ }
+
+ if (depth != 0)
+ throw new ParseException("dc.unterminated.signature");
+ }
+
+ /**
+ * Read Java identifier
+ * Matching pairs of { } are skipped; the text is terminated by the first
+ * unmatched }. It is an error if the beginning of the next tag is detected.
+ */
+ @SuppressWarnings("fallthrough")
+ protected void identifier() throws ParseException {
+ skipWhitespace();
+ int pos = bp;
+
+ if (isJavaIdentifierStart(ch)) {
+ readJavaIdentifier();
+ return;
+ }
+
+ throw new ParseException("dc.identifier.expected");
+ }
+
+ /**
+ * Read a quoted string.
+ * It is an error if the beginning of the next tag is detected.
+ */
+ @SuppressWarnings("fallthrough")
+ protected void quotedString() {
+ int pos = bp;
+ nextChar();
+
+ loop:
+ while (bp < buflen) {
+ switch (ch) {
+ case '\n': case '\r': case '\f':
+ newline = true;
+ break;
+
+ case ' ': case '\t':
+ break;
+
+ case '"':
+ nextChar();
+ // trim trailing white-space?
+ return;
+
+ case '@':
+ if (newline)
+ break loop;
+
+ }
+ nextChar();
+ }
+ }
+
+ /**
+ * Read a term ie. one word.
+ * It is an error if the beginning of the next tag is detected.
+ */
+ @SuppressWarnings("fallthrough")
+ protected void inlineWord() {
+ int pos = bp;
+ int depth = 0;
+ loop:
+ while (bp < buflen) {
+ switch (ch) {
+ case '\n':
+ newline = true;
+ // fallthrough
+
+ case '\r': case '\f': case ' ': case '\t':
+ return;
+
+ case '@':
+ if (newline)
+ break loop;
+
+ case '{':
+ depth++;
+ break;
+
+ case '}':
+ if (depth == 0 || --depth == 0)
+ return;
+ break;
+ }
+ newline = false;
+ nextChar();
+ }
+ }
+
+ /**
+ * Read general text content of an inline tag, including HTML entities and elements.
+ * Matching pairs of { } are skipped; the text is terminated by the first
+ * unmatched }. It is an error if the beginning of the next tag is detected.
+ */
+ @SuppressWarnings("fallthrough")
+ private void inlineContent() {
+
+ skipWhitespace();
+ int pos = bp;
+ int depth = 1;
+
+ loop:
+ while (bp < buflen) {
+
+ switch (ch) {
+ case '\n': case '\r': case '\f':
+ newline = true;
+ // fall through
+
+ case ' ': case '\t':
+ nextChar();
+ break;
+
+ case '&':
+ entity(null);
+ break;
+
+ case '<':
+ newline = false;
+ html();
+ break;
+
+ case '{':
+ newline = false;
+ depth++;
+ nextChar();
+ break;
+
+ case '}':
+ newline = false;
+ if (--depth == 0) {
+ nextChar();
+ return;
+ }
+ nextChar();
+ break;
+
+ case '@':
+ if (newline)
+ break loop;
+ // fallthrough
+
+ default:
+ nextChar();
+ break;
+ }
+ }
+
+ }
+
+ protected void entity(Void list) {
+ newline = false;
+ entity();
+ }
+
+ /**
+ * Read an HTML entity.
+ * {@literal &identifier; } or {@literal &#digits; } or {@literal &#xhex-digits; }
+ */
+ protected void entity() {
+ nextChar();
+ String name = null;
+ if (ch == '#') {
+ int namep = bp;
+ nextChar();
+ if (isDecimalDigit(ch)) {
+ nextChar();
+ while (isDecimalDigit(ch))
+ nextChar();
+ name = new String(buf, namep, bp - namep);
+ } else if (ch == 'x' || ch == 'X') {
+ nextChar();
+ if (isHexDigit(ch)) {
+ nextChar();
+ while (isHexDigit(ch))
+ nextChar();
+ name = new String(buf, namep, bp - namep);
+ }
+ }
+ } else if (isIdentifierStart(ch)) {
+ name = readIdentifier();
+ }
+
+ if (name != null) {
+ if (ch != ';')
+ return;
+ nextChar();
+ }
+ }
+
+ /**
+ * Read the start or end of an HTML tag, or an HTML comment
+ * {@literal <identifier attrs> } or {@literal </identifier> }
+ */
+ protected void html() {
+ int p = bp;
+ nextChar();
+ if (isIdentifierStart(ch)) {
+ String name = readIdentifier();
+ checkHtmlTag(name);
+ htmlAttrs();
+ if (ch == '/') {
+ nextChar();
+ }
+ if (ch == '>') {
+ nextChar();
+ return;
+ }
+ } else if (ch == '/') {
+ nextChar();
+ if (isIdentifierStart(ch)) {
+ readIdentifier();
+ skipWhitespace();
+ if (ch == '>') {
+ nextChar();
+ return;
+ }
+ }
+ } else if (ch == '!') {
+ nextChar();
+ if (ch == '-') {
+ nextChar();
+ if (ch == '-') {
+ nextChar();
+ while (bp < buflen) {
+ int dash = 0;
+ while (ch == '-') {
+ dash++;
+ nextChar();
+ }
+ // Strictly speaking, a comment should not contain "--"
+ // so dash > 2 is an error, dash == 2 implies ch == '>'
+ // See http://www.w3.org/TR/html-markup/syntax.html#syntax-comments
+ // for more details.
+ if (dash >= 2 && ch == '>') {
+ nextChar();
+ return;
+ }
+
+ nextChar();
+ }
+ }
+ }
+ }
+
+ bp = p + 1;
+ ch = buf[bp];
+ }
+
+ /**
+ * Read a series of HTML attributes, terminated by {@literal > }.
+ * Each attribute is of the form {@literal identifier[=value] }.
+ * "value" may be unquoted, single-quoted, or double-quoted.
+ */
+ protected void htmlAttrs() {
+ skipWhitespace();
+
+ loop:
+ while (isIdentifierStart(ch)) {
+ int namePos = bp;
+ String name = readAttributeName();
+ skipWhitespace();
+ StringBuilder value = new StringBuilder();
+ if (ch == '=') {
+ nextChar();
+ skipWhitespace();
+ if (ch == '\'' || ch == '"') {
+ char quote = ch;
+ nextChar();
+ while (bp < buflen && ch != quote) {
+ if (newline && ch == '@') {
+ // No point trying to read more.
+ // In fact, all attrs get discarded by the caller
+ // and superseded by a malformed.html node because
+ // the html tag itself is not terminated correctly.
+ break loop;
+ }
+ value.append(ch);
+ nextChar();
+ }
+ nextChar();
+ } else {
+ while (bp < buflen && !isUnquotedAttrValueTerminator(ch)) {
+ value.append(ch);
+ nextChar();
+ }
+ }
+ skipWhitespace();
+ }
+ checkHtmlAttr(name, value.toString());
+ }
+ }
+
+ protected void attrValueChar(Void list) {
+ switch (ch) {
+ case '&':
+ entity(list);
+ break;
+
+ case '{':
+ inlineTag(list);
+ break;
+
+ default:
+ nextChar();
+ }
+ }
+
+ protected boolean isIdentifierStart(char ch) {
+ return Character.isUnicodeIdentifierStart(ch);
+ }
+
+ protected String readIdentifier() {
+ int start = bp;
+ nextChar();
+ while (bp < buflen && Character.isUnicodeIdentifierPart(ch))
+ nextChar();
+ return new String(buf, start, bp - start);
+ }
+
+ protected String readAttributeName() {
+ int start = bp;
+ nextChar();
+ while (bp < buflen && (Character.isUnicodeIdentifierPart(ch) || ch == '-'))
+ nextChar();
+ return new String(buf, start, bp - start);
+ }
+
+ protected String readTagName() {
+ int start = bp;
+ nextChar();
+ while (bp < buflen
+ && (Character.isUnicodeIdentifierPart(ch) || ch == '.'
+ || ch == '-' || ch == ':')) {
+ nextChar();
+ }
+ return new String(buf, start, bp - start);
+ }
+
+ protected boolean isJavaIdentifierStart(char ch) {
+ return Character.isJavaIdentifierStart(ch);
+ }
+
+ protected String readJavaIdentifier() {
+ int start = bp;
+ nextChar();
+ while (bp < buflen && Character.isJavaIdentifierPart(ch))
+ nextChar();
+ return new String(buf, start, bp - start);
+ }
+
+ protected boolean isDecimalDigit(char ch) {
+ return ('0' <= ch && ch <= '9');
+ }
+
+ protected boolean isHexDigit(char ch) {
+ return ('0' <= ch && ch <= '9')
+ || ('a' <= ch && ch <= 'f')
+ || ('A' <= ch && ch <= 'F');
+ }
+
+ protected boolean isUnquotedAttrValueTerminator(char ch) {
+ switch (ch) {
+ case '\f': case '\n': case '\r': case '\t':
+ case ' ':
+ case '"': case '\'': case '`':
+ case '=': case '<': case '>':
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ protected boolean isWhitespace(char ch) {
+ return Character.isWhitespace(ch);
+ }
+
+ protected void skipWhitespace() {
+ while (isWhitespace(ch)) {
+ nextChar();
+ }
+ }
+
+ /**
+ * @param start position of first character of string
+ * @param end position of character beyond last character to be included
+ */
+ String newString(int start, int end) {
+ return new String(buf, start, end - start);
+ }
+
+ static abstract class TagParser {
+ enum Kind { INLINE, BLOCK }
+
+ final Kind kind;
+ final String name;
+
+
+ TagParser(Kind k, String tk) {
+ kind = k;
+ name = tk;
+ }
+
+ TagParser(Kind k, String tk, boolean retainWhiteSpace) {
+ this(k, tk);
+ }
+
+ Kind getKind() {
+ return kind;
+ }
+
+ String getName() {
+ return name;
+ }
+
+ abstract void parse(int pos) throws ParseException;
+ }
+
+ /**
+ * @see <a href="http://docs.oracle.com/javase/7/docs/technotes/tools/solaris/javadoc.html#javadoctags">Javadoc Tags</a>
+ */
+ @SuppressWarnings("deprecation")
+ private void initTagParsers() {
+ TagParser[] parsers = {
+ // @author name-text
+ new TagParser(Kind.BLOCK, "author") {
+ @Override
+ public void parse(int pos) {
+ blockContent();
+ }
+ },
+
+ // {@code text}
+ new TagParser(Kind.INLINE, "code", true) {
+ @Override
+ public void parse(int pos) throws ParseException {
+ inlineText(WhitespaceRetentionPolicy.REMOVE_FIRST_SPACE);
+ nextChar();
+ }
+ },
+
+ // @deprecated deprecated-text
+ new TagParser(Kind.BLOCK, "deprecated") {
+ @Override
+ public void parse(int pos) {
+ blockContent();
+ }
+ },
+
+ // {@docRoot}
+ new TagParser(Kind.INLINE, "docRoot") {
+ @Override
+ public void parse(int pos) throws ParseException {
+ if (ch == '}') {
+ nextChar();
+ return;
+ }
+ inlineText(WhitespaceRetentionPolicy.REMOVE_ALL); // skip unexpected content
+ nextChar();
+ throw new ParseException("dc.unexpected.content");
+ }
+ },
+
+ // @exception class-name description
+ new TagParser(Kind.BLOCK, "exception") {
+ @Override
+ public void parse(int pos) throws ParseException {
+ skipWhitespace();
+ reference(false);
+ blockContent();
+ }
+ },
+
+ // @hidden hidden-text
+ new TagParser(Kind.BLOCK, "hidden") {
+ @Override
+ public void parse(int pos) {
+ blockContent();
+ }
+ },
+
+ // @index search-term options-description
+ new TagParser(Kind.INLINE, "index") {
+ @Override
+ public void parse(int pos) throws ParseException {
+ skipWhitespace();
+ if (ch == '}') {
+ throw new ParseException("dc.no.content");
+ }
+ if (ch == '"') quotedString(); else inlineWord();
+ skipWhitespace();
+ if (ch != '}') {
+ inlineContent();
+ } else {
+ nextChar();
+ }
+ }
+ },
+
+ // {@inheritDoc}
+ new TagParser(Kind.INLINE, "inheritDoc") {
+ @Override
+ public void parse(int pos) throws ParseException {
+ if (ch == '}') {
+ nextChar();
+ return;
+ }
+ inlineText(WhitespaceRetentionPolicy.REMOVE_ALL); // skip unexpected content
+ nextChar();
+ throw new ParseException("dc.unexpected.content");
+ }
+ },
+
+ // {@link package.class#member label}
+ new TagParser(Kind.INLINE, "link") {
+ @Override
+ public void parse(int pos) throws ParseException {
+ reference(true);
+ inlineContent();
+ }
+ },
+
+ // {@linkplain package.class#member label}
+ new TagParser(Kind.INLINE, "linkplain") {
+ @Override
+ public void parse(int pos) throws ParseException {
+ reference(true);
+ inlineContent();
+ }
+ },
+
+ // {@literal text}
+ new TagParser(Kind.INLINE, "literal", true) {
+ @Override
+ public void parse(int pos) throws ParseException {
+ inlineText(WhitespaceRetentionPolicy.REMOVE_FIRST_SPACE);
+ nextChar();
+ }
+ },
+
+ // @param parameter-name description
+ new TagParser(Kind.BLOCK, "param") {
+ @Override
+ public void parse(int pos) throws ParseException {
+ skipWhitespace();
+
+ boolean typaram = false;
+ if (ch == '<') {
+ typaram = true;
+ nextChar();
+ }
+
+ identifier();
+
+ if (typaram) {
+ if (ch != '>')
+ throw new ParseException("dc.gt.expected");
+ nextChar();
+ }
+
+ skipWhitespace();
+ blockContent();
+ }
+ },
+
+ // @return description
+ new TagParser(Kind.BLOCK, "return") {
+ @Override
+ public void parse(int pos) {
+ blockContent();
+ }
+ },
+
+ // @see reference | quoted-string | HTML
+ new TagParser(Kind.BLOCK, "see") {
+ @Override
+ public void parse(int pos) throws ParseException {
+ skipWhitespace();
+ switch (ch) {
+ case '"':
+ quotedString();
+ skipWhitespace();
+ if (ch == '@'
+ || ch == EOI && bp == buf.length - 1) {
+ return;
+ }
+ break;
+
+ case '<':
+ blockContent();
+ return;
+
+ case '@':
+ if (newline)
+ throw new ParseException("dc.no.content");
+ break;
+
+ case EOI:
+ if (bp == buf.length - 1)
+ throw new ParseException("dc.no.content");
+ break;
+
+ default:
+ if (isJavaIdentifierStart(ch) || ch == '#') {
+ reference(true);
+ blockContent();
+ }
+ }
+ throw new ParseException("dc.unexpected.content");
+ }
+ },
+
+ // @serialData data-description
+ new TagParser(Kind.BLOCK, "@serialData") {
+ @Override
+ public void parse(int pos) {
+ blockContent();
+ }
+ },
+
+ // @serialField field-name field-type description
+ new TagParser(Kind.BLOCK, "serialField") {
+ @Override
+ public void parse(int pos) throws ParseException {
+ skipWhitespace();
+ identifier();
+ skipWhitespace();
+ reference(false);
+ if (isWhitespace(ch)) {
+ skipWhitespace();
+ blockContent();
+ }
+ }
+ },
+
+ // @serial field-description | include | exclude
+ new TagParser(Kind.BLOCK, "serial") {
+ @Override
+ public void parse(int pos) {
+ blockContent();
+ }
+ },
+
+ // @since since-text
+ new TagParser(Kind.BLOCK, "since") {
+ @Override
+ public void parse(int pos) {
+ blockContent();
+ }
+ },
+
+ // @throws class-name description
+ new TagParser(Kind.BLOCK, "throws") {
+ @Override
+ public void parse(int pos) throws ParseException {
+ skipWhitespace();
+ reference(false);
+ blockContent();
+ }
+ },
+
+ // {@value package.class#field}
+ new TagParser(Kind.INLINE, "value") {
+ @Override
+ public void parse(int pos) throws ParseException {
+ reference(true);
+ skipWhitespace();
+ if (ch == '}') {
+ nextChar();
+ return;
+ }
+ nextChar();
+ throw new ParseException("dc.unexpected.content");
+ }
+ },
+
+ // @version version-text
+ new TagParser(Kind.BLOCK, "version") {
+ @Override
+ public void parse(int pos) {
+ blockContent();
+ }
+ },
+ };
+
+ tagParsers = new HashMap<>();
+ for (TagParser p: parsers)
+ tagParsers.put(p.getName(), p);
+
+ }
+
+ private void initEventAttrs() {
+ eventAttrs = new HashSet<>(Arrays.asList(
+ // See https://www.w3.org/TR/html-markup/global-attributes.html#common.attrs.event-handler
+ "onabort", "onblur", "oncanplay", "oncanplaythrough",
+ "onchange", "onclick", "oncontextmenu", "ondblclick",
+ "ondrag", "ondragend", "ondragenter", "ondragleave",
+ "ondragover", "ondragstart", "ondrop", "ondurationchange",
+ "onemptied", "onended", "onerror", "onfocus", "oninput",
+ "oninvalid", "onkeydown", "onkeypress", "onkeyup",
+ "onload", "onloadeddata", "onloadedmetadata", "onloadstart",
+ "onmousedown", "onmousemove", "onmouseout", "onmouseover",
+ "onmouseup", "onmousewheel", "onpause", "onplay",
+ "onplaying", "onprogress", "onratechange", "onreadystatechange",
+ "onreset", "onscroll", "onseeked", "onseeking",
+ "onselect", "onshow", "onstalled", "onsubmit", "onsuspend",
+ "ontimeupdate", "onvolumechange", "onwaiting",
+
+ // See https://www.w3.org/TR/html4/sgml/dtd.html
+ // Most of the attributes that take a %Script are also defined as event handlers
+ // in HTML 5. The one exception is onunload.
+ // "onchange", "onclick", "ondblclick", "onfocus",
+ // "onkeydown", "onkeypress", "onkeyup", "onload",
+ // "onmousedown", "onmousemove", "onmouseout", "onmouseover",
+ // "onmouseup", "onreset", "onselect", "onsubmit",
+ "onunload"
+ ));
+ }
+
+ private void initURIAttrs() {
+ uriAttrs = new HashSet<>(Arrays.asList(
+ // See https://www.w3.org/TR/html4/sgml/dtd.html
+ // https://www.w3.org/TR/html5/
+ // These are all the attributes that take a %URI or a valid URL potentially surrounded
+ // by spaces
+ "action", "cite", "classid", "codebase", "data",
+ "datasrc", "for", "href", "longdesc", "profile",
+ "src", "usemap"
+ ));
+ }
+
+}
--- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/main/RootDocImpl.java Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/main/RootDocImpl.java Tue Jul 12 14:41:14 2016 -0700
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2016, 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
@@ -383,6 +383,10 @@
env.initDoclint(opts, customTagNames, htmlVersion);
}
+ public JavaScriptScanner initJavaScriptScanner(boolean allowScriptInComments) {
+ return env.initJavaScriptScanner(allowScriptInComments);
+ }
+
public boolean isFunctionalInterface(AnnotationDesc annotationDesc) {
return env.source.allowLambda()
&& annotationDesc.annotationType().qualifiedName().equals(
--- a/langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/resources/javadoc.properties Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/com/sun/tools/javadoc/resources/javadoc.properties Tue Jul 12 14:41:14 2016 -0700
@@ -145,6 +145,8 @@
javadoc.Body_missing_from_html_file=Body tag missing from HTML file
javadoc.End_body_missing_from_html_file=Close body tag missing from HTML file
javadoc.Multiple_package_comments=Multiple sources of package comments found for package "{0}"
+javadoc.JavaScript_in_comment=JavaScript found in documentation comment.\n\
+ Use --allow-script-in-comments to allow use of JavaScript.
javadoc.class_not_found=Class {0} not found.
javadoc.error=error
javadoc.warning=warning
--- a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ConfigurationImpl.java Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ConfigurationImpl.java Tue Jul 12 14:41:14 2016 -0700
@@ -296,9 +296,21 @@
return false;
}
}
+
+ // In a more object-oriented world, this would be done by methods on the Option objects.
+ // Note that -windowtitle silently removes any and all HTML elements, and so does not need
+ // to be handled here.
+ utils.checkJavaScriptInOption("-header", header);
+ utils.checkJavaScriptInOption("-footer", footer);
+ utils.checkJavaScriptInOption("-top", top);
+ utils.checkJavaScriptInOption("-bottom", bottom);
+ utils.checkJavaScriptInOption("-doctitle", doctitle);
+ utils.checkJavaScriptInOption("-packagesheader", packagesheader);
+
return true;
}
+
@Override
public boolean finishOptionSettings() {
if (!validateOptions()) {
--- a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard.properties Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/standard.properties Tue Jul 12 14:41:14 2016 -0700
@@ -363,6 +363,9 @@
doclet.usage.no-frames.description=\
Disable the use of frames in the generated output
+doclet.usage.allow-script-in-comments.description=\
+ Allow JavaScript in options and comments
+
doclet.usage.xdocrootparent.parameters=\
<url>
doclet.usage.xdocrootparent.description=\
--- a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/AbstractDoclet.java Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/AbstractDoclet.java Tue Jul 12 14:41:14 2016 -0700
@@ -40,6 +40,7 @@
import jdk.javadoc.internal.doclets.toolkit.builders.BuilderFactory;
import jdk.javadoc.internal.doclets.toolkit.util.ClassTree;
import jdk.javadoc.internal.doclets.toolkit.util.DocFileIOException;
+import jdk.javadoc.internal.doclets.toolkit.util.UncheckedDocletException;
import jdk.javadoc.internal.doclets.toolkit.util.InternalException;
import jdk.javadoc.internal.doclets.toolkit.util.PackageListWriter;
import jdk.javadoc.internal.doclets.toolkit.util.ResourceIOException;
@@ -112,8 +113,12 @@
}
try {
- startGeneration(docEnv);
- return true;
+ try {
+ startGeneration(docEnv);
+ return true;
+ } catch (UncheckedDocletException e) {
+ throw (DocletException) e.getCause();
+ }
} catch (DocFileIOException e) {
switch (e.mode) {
--- a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/CommentUtils.java Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/CommentUtils.java Tue Jul 12 14:41:14 2016 -0700
@@ -34,6 +34,8 @@
package jdk.javadoc.internal.doclets.toolkit;
+import java.net.URI;
+
import com.sun.source.doctree.DocCommentTree;
import com.sun.source.doctree.DocTree;
import com.sun.source.doctree.IdentifierTree;
@@ -43,9 +45,11 @@
import com.sun.source.util.DocTreePath;
import com.sun.source.util.DocTrees;
import com.sun.source.util.TreePath;
+
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
@@ -54,6 +58,11 @@
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.Elements;
import javax.tools.FileObject;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+
+import com.sun.tools.javac.util.DefinedBy;
+import com.sun.tools.javac.util.DefinedBy.Api;
import jdk.javadoc.internal.doclets.toolkit.util.Utils;
public class CommentUtils {
@@ -185,6 +194,16 @@
return new DocCommentDuo(treePath.getTreePath(), dcTree);
}
+ public DocCommentTree parse(URI uri, String text) {
+ return trees.getDocCommentTree(new SimpleJavaFileObject(
+ uri, JavaFileObject.Kind.SOURCE) {
+ @Override @DefinedBy(Api.COMPILER)
+ public CharSequence getCharContent(boolean ignoreEncoding) {
+ return text;
+ }
+ });
+ }
+
public void setDocCommentTree(Element element, List<DocTree> fullBody,
List<DocTree> blockTags, Utils utils) {
DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, blockTags);
--- a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/Configuration.java Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/Configuration.java Tue Jul 12 14:41:14 2016 -0700
@@ -228,6 +228,11 @@
public boolean showversion = false;
/**
+ * Allow JavaScript in doc comments.
+ */
+ private boolean allowScriptInComments = false;
+
+ /**
* Sourcepath from where to read the source files. Default is classpath.
*
*/
@@ -646,6 +651,13 @@
dumpOnError = true;
return true;
}
+ },
+ new Option(resources, "--allow-script-in-comments") {
+ @Override
+ public boolean process(String opt, List<String> args) {
+ allowScriptInComments = true;
+ return true;
+ }
}
};
Set<Doclet.Option> set = new TreeSet<>();
@@ -1054,7 +1066,7 @@
private final int argCount;
protected Option(Resources resources, String name, int argCount) {
- this(resources, "doclet.usage." + name.toLowerCase().replaceAll("^-*", ""), name, argCount);
+ this(resources, "doclet.usage." + name.toLowerCase().replaceAll("^-+", ""), name, argCount);
}
protected Option(Resources resources, String keyBase, String name, int argCount) {
@@ -1228,4 +1240,13 @@
}
}
}
+
+ /**
+ * Returns whether or not to allow JavaScript in comments.
+ * Default is off; can be set true from a command line option.
+ * @return the allowScriptInComments
+ */
+ public boolean isAllowScriptInComments() {
+ return allowScriptInComments;
+ }
}
--- a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/builders/AbstractBuilder.java Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/builders/AbstractBuilder.java Tue Jul 12 14:41:14 2016 -0700
@@ -35,6 +35,7 @@
import jdk.javadoc.internal.doclets.toolkit.DocletException;
import jdk.javadoc.internal.doclets.toolkit.Messages;
import jdk.javadoc.internal.doclets.toolkit.Resources;
+import jdk.javadoc.internal.doclets.toolkit.util.UncheckedDocletException;
import jdk.javadoc.internal.doclets.toolkit.util.InternalException;
import jdk.javadoc.internal.doclets.toolkit.util.SimpleDocletException;
import jdk.javadoc.internal.doclets.toolkit.util.Utils;
@@ -167,6 +168,8 @@
Throwable cause = e.getCause();
if (cause instanceof DocletException) {
throw (DocletException) cause;
+ } else if (cause instanceof UncheckedDocletException) {
+ throw (DocletException) cause.getCause();
} else {
// use InternalException, so that a stacktrace showing the position of
// the internal exception is generated
--- a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets.properties Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets.properties Tue Jul 12 14:41:14 2016 -0700
@@ -42,6 +42,10 @@
doclet.Building_Index=Building index for all the packages and classes...
doclet.Building_Index_For_All_Classes=Building index for all classes...
doclet.sourcetab_warning=The argument for -sourcetab must be an integer greater than 0.
+doclet.JavaScript_in_comment=JavaScript found in documentation comment.\n\
+Use --allow-script-in-comments to allow use of JavaScript.
+doclet.JavaScript_in_option=option {0} contains JavaScript.\n\
+Use --allow-script-in-comments to allow use of JavaScript.
doclet.Packages=Packages
doclet.Modules=Modules
doclet.Other_Packages=Other Packages
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/JavaScriptScanner.java Tue Jul 12 14:41:14 2016 -0700
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.javadoc.internal.doclets.toolkit.util;
+
+
+import java.util.List;
+import java.util.Locale;
+import java.util.function.Consumer;
+
+import com.sun.source.doctree.AttributeTree;
+import com.sun.source.doctree.DocCommentTree;
+import com.sun.source.doctree.DocTree;
+import com.sun.source.doctree.DocTree.Kind;
+import com.sun.source.doctree.StartElementTree;
+import com.sun.source.util.DocTreePath;
+import com.sun.source.util.DocTreePathScanner;
+import com.sun.source.util.TreePath;
+import com.sun.tools.javac.util.DefinedBy;
+import com.sun.tools.javac.util.DefinedBy.Api;
+
+/**
+ * A DocTree scanner to detect use of JavaScript in a doc comment tree.
+ */
+public class JavaScriptScanner extends DocTreePathScanner<Void, Consumer<DocTreePath>> {
+
+ public Void scan(DocCommentTree tree, TreePath p, Consumer<DocTreePath> f) {
+ return scan(new DocTreePath(p, tree), f);
+ }
+
+ @Override @DefinedBy(Api.COMPILER_TREE)
+ public Void visitStartElement(StartElementTree tree, Consumer<DocTreePath> f) {
+ String name = tree.getName().toString();
+ if (name.equalsIgnoreCase("script"))
+ f.accept(getCurrentPath());
+ return super.visitStartElement(tree, f);
+ }
+
+ @Override @DefinedBy(Api.COMPILER_TREE)
+ public Void visitAttribute(AttributeTree tree, Consumer<DocTreePath> f) {
+ String name = tree.getName().toString().toLowerCase(Locale.ENGLISH);
+ switch (name) {
+ // See https://www.w3.org/TR/html-markup/global-attributes.html#common.attrs.event-handler
+ case "onabort": case "onblur": case "oncanplay": case "oncanplaythrough":
+ case "onchange": case "onclick": case "oncontextmenu": case "ondblclick":
+ case "ondrag": case "ondragend": case "ondragenter": case "ondragleave":
+ case "ondragover": case "ondragstart": case "ondrop": case "ondurationchange":
+ case "onemptied": case "onended": case "onerror": case "onfocus": case "oninput":
+ case "oninvalid": case "onkeydown": case "onkeypress": case "onkeyup":
+ case "onload": case "onloadeddata": case "onloadedmetadata": case "onloadstart":
+ case "onmousedown": case "onmousemove": case "onmouseout": case "onmouseover":
+ case "onmouseup": case "onmousewheel": case "onpause": case "onplay":
+ case "onplaying": case "onprogress": case "onratechange": case "onreadystatechange":
+ case "onreset": case "onscroll": case "onseeked": case "onseeking":
+ case "onselect": case "onshow": case "onstalled": case "onsubmit": case "onsuspend":
+ case "ontimeupdate": case "onvolumechange": case "onwaiting":
+
+ // See https://www.w3.org/TR/html4/sgml/dtd.html
+ // Most of the attributes that take a %Script are also defined as event handlers
+ // in HTML 5. The one exception is onunload.
+ // case "onchange": case "onclick": case "ondblclick": case "onfocus":
+ // case "onkeydown": case "onkeypress": case "onkeyup": case "onload":
+ // case "onmousedown": case "onmousemove": case "onmouseout": case "onmouseover":
+ // case "onmouseup": case "onreset": case "onselect": case "onsubmit":
+ case "onunload":
+ f.accept(getCurrentPath());
+ break;
+
+ // See https://www.w3.org/TR/html4/sgml/dtd.html
+ // https://www.w3.org/TR/html5/
+ // These are all the attributes that take a %URI or a valid URL potentially surrounded
+ // by spaces
+ case "action": case "cite": case "classid": case "codebase": case "data":
+ case "datasrc": case "for": case "href": case "longdesc": case "profile":
+ case "src": case "usemap":
+ List<? extends DocTree> value = tree.getValue();
+ if (!value.isEmpty() && value.get(0).getKind() == Kind.TEXT) {
+ String v = value.get(0).toString().trim().toLowerCase(Locale.ENGLISH);
+ if (v.startsWith("javascript:")) {
+ f.accept(getCurrentPath());
+ }
+ }
+ break;
+ }
+ return super.visitAttribute(tree, f);
+ }
+
+ /**
+ * Used to indicate a fault when parsing, typically used in
+ * lambda methods.
+ */
+ public static class Fault extends RuntimeException {
+ private static final long serialVersionUID = 0L;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/UncheckedDocletException.java Tue Jul 12 14:41:14 2016 -0700
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.javadoc.internal.doclets.toolkit.util;
+
+import jdk.javadoc.internal.doclets.toolkit.DocletException;
+
+/**
+ * An unchecked exception that wraps a DocletException.
+ * It can be used in places where a checked exception
+ * is not permitted, such as in lambda expressions.
+ *
+ * <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 UncheckedDocletException extends Error {
+ private static final long serialVersionUID = -9131058909576418984L;
+
+ public UncheckedDocletException(DocletException de) {
+ super(de);
+ }
+
+ @Override
+ public synchronized Throwable getCause() {
+ return super.getCause();
+ }
+
+ @Override
+ public synchronized Throwable initCause(Throwable cause) {
+ throw new UnsupportedOperationException();
+ }
+}
--- a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java Tue Jul 12 14:41:14 2016 -0700
@@ -27,6 +27,7 @@
import java.lang.annotation.Documented;
import java.lang.ref.SoftReference;
+import java.net.URI;
import java.text.CollationKey;
import java.text.Collator;
import java.util.*;
@@ -108,6 +109,7 @@
public final DocTrees docTrees;
public final Elements elementUtils;
public final Types typeUtils;
+ public final JavaScriptScanner javaScriptScanner;
public Utils(Configuration c) {
configuration = c;
@@ -115,6 +117,7 @@
elementUtils = c.docEnv.getElementUtils();
typeUtils = c.docEnv.getTypeUtils();
docTrees = c.docEnv.getDocTrees();
+ javaScriptScanner = c.isAllowScriptInComments() ? null : new JavaScriptScanner();
}
// our own little symbol table
@@ -3025,6 +3028,16 @@
TreePath path = isValidDuo(duo) ? duo.treePath : null;
if (!dcTreeCache.containsKey(element)) {
if (docCommentTree != null && path != null) {
+ if (!configuration.isAllowScriptInComments()) {
+ try {
+ javaScriptScanner.scan(docCommentTree, path, p -> {
+ throw new JavaScriptScanner.Fault();
+ });
+ } catch (JavaScriptScanner.Fault jsf) {
+ String text = configuration.getText("doclet.JavaScript_in_comment");
+ throw new UncheckedDocletException(new SimpleDocletException(text, jsf));
+ }
+ }
configuration.workArounds.runDocLint(path);
}
dcTreeCache.put(element, duo);
@@ -3044,6 +3057,21 @@
return null;
}
+ public void checkJavaScriptInOption(String name, String value) {
+ if (!configuration.isAllowScriptInComments()) {
+ DocCommentTree dct = configuration.cmtUtils.parse(
+ URI.create("option://" + name.replace("-", "")), "<body>" + value + "</body>");
+ try {
+ javaScriptScanner.scan(dct, null, p -> {
+ throw new JavaScriptScanner.Fault();
+ });
+ } catch (JavaScriptScanner.Fault jsf) {
+ String text = configuration.getText("doclet.JavaScript_in_option", name);
+ throw new UncheckedDocletException(new SimpleDocletException(text, jsf));
+ }
+ }
+ }
+
boolean isValidDuo(DocCommentDuo duo) {
return duo != null && duo.dcTree != null;
}
--- a/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/Messager.java Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/Messager.java Tue Jul 12 14:41:14 2016 -0700
@@ -182,7 +182,7 @@
}
private String getDiagSource(DocTreePath path) {
- if (path == null) {
+ if (path == null || path.getTreePath() == null) {
return programName;
}
JavacTrees trees = JavacTrees.instance(context);
--- a/langtools/test/Makefile Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/test/Makefile Tue Jul 12 14:41:14 2016 -0700
@@ -378,7 +378,7 @@
@rm -f -r $(JCK_COMPILER_OUTPUT_DIR)/work $(JCK_COMPILER_OUTPUT_DIR)/report \
$(JCK_COMPILER_OUTPUT_DIR)/diff.html $(JCK_COMPILER_OUTPUT_DIR)/status.txt
@mkdir -p $(JCK_COMPILER_OUTPUT_DIR)
- $(JT_JAVA)/bin/java -Xmx512m \
+ $(JT_JAVA)/bin/java -Xmx1024m \
-jar $(JCK_HOME)/JCK-compiler-9/lib/jtjck.jar \
$(if $(JCK_VERBOSE),$(if $(filter $(JCK_VERBOSE),summary),-v,-v:$(JCK_VERBOSE))) \
-r:$(JCK_COMPILER_OUTPUT_DIR)/report \
@@ -429,7 +429,7 @@
@rm -f -r $(JCK_RUNTIME_OUTPUT_DIR)/work $(JCK_RUNTIME_OUTPUT_DIR)/report \
$(JCK_RUNTIME_OUTPUT_DIR)/diff.html $(JCK_RUNTIME_OUTPUT_DIR)/status.txt
@mkdir -p $(JCK_RUNTIME_OUTPUT_DIR)
- $(JT_JAVA)/bin/java -Xmx512m \
+ $(JT_JAVA)/bin/java -Xmx1024m \
-jar $(JCK_HOME)/JCK-runtime-9/lib/jtjck.jar \
$(if $(JCK_VERBOSE),$(if $(filter $(JCK_VERBOSE),summary),-v,-v:$(JCK_VERBOSE))) \
-r:$(JCK_RUNTIME_OUTPUT_DIR)/report \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/jdk/javadoc/tool/TestScriptInComment.java Tue Jul 12 14:41:14 2016 -0700
@@ -0,0 +1,313 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * @test
+ * @bug 8138725
+ * @summary test --allow-script-in-comments
+ * @modules jdk.javadoc/jdk.javadoc.internal.tool
+ */
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Combo-style test, exercising combinations of different HTML fragments that may contain
+ * JavaScript, different places to place those fragments, and whether or not to allow the use
+ * of JavaScript.
+ */
+public class TestScriptInComment {
+ public static void main(String... args) throws Exception {
+ new TestScriptInComment().run();
+ }
+
+ /**
+ * Representative samples of different fragments of HTML that may contain JavaScript.
+ * To facilitate checking the output manually in a browser, the text "#ALERT" will be
+ * replaced by a JavaScript call of "alert(msg)", using a message string that is specific
+ * to the test case.
+ */
+ enum Comment {
+ LC("<script>#ALERT</script>", true), // script tag in Lower Case
+ UC("<SCRIPT>#ALERT</script>", true), // script tag in Upper Case
+ WS("< script >#ALERT</script>", false, "-Xdoclint:none"), // script tag with invalid white space
+ SP("<script src=\"file\"> #ALERT </script>", true), // script tag with an attribute
+ ON("<a onclick='#ALERT'>x</a>", true), // event handler attribute
+ URI("<a href='javascript:#ALERT'>x</a>", true); // javascript URI
+
+ /**
+ * Creates an HTML fragment to be injected into a template.
+ * @param text the HTML fragment to put into a doc comment or option.
+ * @param hasScript whether or not this fragment does contain legal JavaScript
+ * @param opts any additional options to be specified when javadoc is run
+ */
+ Comment(String text, boolean hasScript, String... opts) {
+ this.text = text;
+ this.hasScript = hasScript;
+ this.opts = Arrays.asList(opts);
+ }
+
+ final String text;
+ final boolean hasScript;
+ final List<String> opts;
+ };
+
+ /**
+ * Representative samples of positions in which javadoc may find JavaScript.
+ * Each template contains a series of strings, which are written to files or inferred as options.
+ * The first source file implies a corresponding output file which should not be written
+ * if the comment contains JavaScript and JavaScript is not allowed.
+ */
+ enum Template {
+ OVR("<html><body> overview #COMMENT </body></html>", "package p; public class C { }"),
+ PKGINFO("#COMMENT package p;", "package p; public class C { }"),
+ PKGHTML("<html><body>#COMMENT package p;</body></html>", "package p; public class C { }"),
+ CLS("package p; #COMMENT public class C { }"),
+ CON("package p; public class C { #COMMENT public C() { } }"),
+ FLD("package p; public class C { #COMMENT public int f; }"),
+ MTH("package p; public class C { #COMMENT public void m() { } }"),
+ TOP("-top", "lorem #COMMENT ipsum", "package p; public class C { }"),
+ HDR("-header", "lorem #COMMENT ipsum", "package p; public class C { }"),
+ FTR("-footer", "lorem #COMMENT ipsum", "package p; public class C { }"),
+ BTM("-bottom", "lorem #COMMENT ipsum", "package p; public class C { }"),
+ DTTL("-doctitle", "lorem #COMMENT ipsum", "package p; public class C { }"),
+ PHDR("-packagesheader", "lorem #COMMENT ipsum", "package p; public class C { }");
+
+ Template(String... args) {
+ opts = new ArrayList<String>();
+ sources = new ArrayList<String>();
+ int i = 0;
+ while (args[i].startsWith("-")) {
+ // all options being tested have a single argument that follow the option
+ opts.add(args[i++]);
+ opts.add(args[i++]);
+ }
+ while(i < args.length) {
+ sources.add(args[i++]);
+ }
+ }
+
+ // groups: 1 <html> or not; 2: package name; 3: class name
+ private final Pattern pat =
+ Pattern.compile("(?i)(<html>)?.*?(?:package ([a-z]+);.*?(?:class ([a-z]+).*)?)?");
+
+ /**
+ * Infer the file in which to write the given source.
+ * @param dir the base source directory
+ * @param src the source text
+ * @return the file in which the source should be written
+ */
+ File getSrcFile(File srcDir, String src) {
+ String f;
+ Matcher m = pat.matcher(src);
+ if (!m.matches())
+ throw new Error("match failed");
+ if (m.group(3) != null) {
+ f = m.group(2) + "/" + m.group(3) + ".java";
+ } else if (m.group(2) != null) {
+ f = m.group(2) + "/" + (m.group(1) == null ? "package-info.java" : "package.html");
+ } else {
+ f = "overview.html";
+ }
+ return new File(srcDir, f);
+ }
+
+ /**
+ * Get the options to give to javadoc.
+ * @param srcDir the srcDir to use -overview is needed
+ * @return
+ */
+ List<String> getOpts(File srcDir) {
+ if (!opts.isEmpty()) {
+ return opts;
+ } else if (sources.get(0).contains("overview")) {
+ return Arrays.asList("-overview", getSrcFile(srcDir, sources.get(0)).getPath());
+ } else {
+ return Collections.emptyList();
+ }
+ }
+
+ /**
+ * Gets the output file corresponding to the first source file.
+ * This file should not be written if the comment contains JavaScript and JavaScripot is
+ * not allowed.
+ * @param dir the base output directory
+ * @return the output file
+ */
+ File getOutFile(File outDir) {
+ String f;
+ Matcher m = pat.matcher(sources.get(0));
+ if (!m.matches())
+ throw new Error("match failed");
+ if (m.group(3) != null) {
+ f = m.group(2) + "/" + m.group(3) + ".html";
+ } else if (m.group(2) != null) {
+ f = m.group(2) + "/package-summary.html";
+ } else {
+ f = "overview-summary.html";
+ }
+ return new File(outDir, f);
+ }
+
+ final List<String> opts;
+ final List<String> sources;
+ };
+
+ enum Option {
+ OFF(null),
+ ON("--allow-script-in-comments");
+
+ Option(String text) {
+ this.text = text;
+ }
+
+ final String text;
+ };
+
+ private PrintStream out = System.err;
+
+ public void run() throws Exception {
+ int count = 0;
+ for (Template template: Template.values()) {
+ for (Comment comment: Comment.values()) {
+ for (Option option: Option.values()) {
+ if (test(template, comment, option)) {
+ count++;
+ }
+ }
+ }
+ }
+
+ out.println(count + " test cases run");
+ if (errors > 0) {
+ throw new Exception(errors + " errors occurred");
+ }
+ }
+
+ boolean test(Template template, Comment comment, Option option) throws IOException {
+ if (option == Option.ON && !comment.hasScript) {
+ // skip --allowScriptInComments if comment does not contain JavaScript
+ return false;
+ }
+
+ String test = template + "-" + comment + "-" + option;
+ out.println("Test: " + test);
+
+ File dir = new File(test);
+ dir.mkdirs();
+ File srcDir = new File(dir, "src");
+ File outDir = new File(dir, "out");
+
+ String alert = "alert(\"" + test + "\");";
+ for (String src: template.sources) {
+ writeFile(template.getSrcFile(srcDir, src),
+ src.replace("#COMMENT",
+ "/** " + comment.text.replace("#ALERT", alert) + " **/"));
+ }
+
+ List<String> opts = new ArrayList<String>();
+ opts.add("-sourcepath");
+ opts.add(srcDir.getPath());
+ opts.add("-d");
+ opts.add(outDir.getPath());
+ if (option.text != null)
+ opts.add(option.text);
+ for (String opt: template.getOpts(srcDir)) {
+ opts.add(opt.replace("#COMMENT", comment.text.replace("#ALERT", alert)));
+ }
+ opts.addAll(comment.opts);
+ opts.add("-noindex"); // index not required; save time/space writing files
+ opts.add("p");
+
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ int rc = javadoc(opts, pw);
+ pw.close();
+ String log = sw.toString();
+ writeFile(new File(dir, "log.txt"), log);
+
+ out.println("opts: " + opts);
+ out.println(" rc: " + rc);
+ out.println(" log:");
+ out.println(log);
+
+ String ERROR = "Use --allow-script-in-comment";
+ File outFile = template.getOutFile(outDir);
+
+ boolean expectErrors = comment.hasScript && (option == Option.OFF);
+
+ if (expectErrors) {
+ check(rc != 0, "unexpected exit code: " + rc);
+ check(log.contains(ERROR), "expected error message not found");
+ check(!outFile.exists(), "output file found unexpectedly");
+ } else {
+ check(rc == 0, "unexpected exit code: " + rc);
+ check(!log.contains(ERROR), "error message found");
+ check(outFile.exists(), "output file not found");
+ }
+
+ out.println();
+ return true;
+ }
+
+ int javadoc(List<String> opts, PrintWriter pw) {
+ return jdk.javadoc.internal.tool.Main.execute(opts.toArray(new String[opts.size()]), pw);
+ }
+
+ File writeFile(File f, String text) throws IOException {
+ f.getParentFile().mkdirs();
+ FileWriter fw = new FileWriter(f);
+ try {
+ fw.write(text);
+ } finally {
+ fw.close();
+ }
+ return f;
+ }
+
+ void check(boolean cond, String errMessage) {
+ if (!cond) {
+ error(errMessage);
+ }
+ }
+
+ void error(String message) {
+ out.println("Error: " + message);
+ errors++;
+ }
+
+ int errors = 0;
+}
+
--- a/langtools/test/tools/doclint/html/OtherTagsTest.out Tue Jan 17 09:40:23 2017 -0800
+++ b/langtools/test/tools/doclint/html/OtherTagsTest.out Tue Jul 12 14:41:14 2016 -0700
@@ -19,10 +19,7 @@
OtherTagsTest.java:21: error: element not allowed in documentation comments: <noframes>
* <noframes> </noframes>
^
-OtherTagsTest.java:22: error: element not allowed in documentation comments: <script>
- * <script> </script>
- ^
OtherTagsTest.java:23: error: element not allowed in documentation comments: <title>
* <title> </title>
^
-9 errors
+8 errors
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javadoc/TestScriptInComment.java Tue Jul 12 14:41:14 2016 -0700
@@ -0,0 +1,314 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * @test
+ * @bug 8138725
+ * @summary test --allow-script-in-comments
+ * @modules jdk.javadoc/com.sun.tools.javadoc
+ */
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Combo-style test, exercising combinations of different HTML fragments that may contain
+ * JavaScript, different places to place those fragments, and whether or not to allow the use
+ * of JavaScript.
+ */
+public class TestScriptInComment {
+ public static void main(String... args) throws Exception {
+ new TestScriptInComment().run();
+ }
+
+ /**
+ * Representative samples of different fragments of HTML that may contain JavaScript.
+ * To facilitate checking the output manually in a browser, the text "#ALERT" will be
+ * replaced by a JavaScript call of "alert(msg)", using a message string that is specific
+ * to the test case.
+ */
+ enum Comment {
+ LC("<script>#ALERT</script>", true), // script tag in Lower Case
+ UC("<SCRIPT>#ALERT</script>", true), // script tag in Upper Case
+ WS("< script >#ALERT</script>", false, "-Xdoclint:none"), // script tag with invalid white space
+ SA("<script src=\"file\"> #ALERT </script>", true), // script tag with an attribute
+ ON("<a onclick='#ALERT'>x</a>", true), // event handler attribute
+ URI("<a href='javascript:#ALERT'>x</a>", true); // javadcript URI
+
+ /**
+ * Creates an HTML fragment to be injected into a template.
+ * @param text the HTML fragment to put into a doc comment or option.
+ * @param hasScript whether or not this fragment does contain legal JavaScript
+ * @param opts any additional options to be specified when javadoc is run
+ */
+ Comment(String text, boolean hasScript, String... opts) {
+ this.text = text;
+ this.hasScript = hasScript;
+ this.opts = Arrays.asList(opts);
+ }
+
+ final String text;
+ final boolean hasScript;
+ final List<String> opts;
+ };
+
+ /**
+ * Representative samples of positions in which javadoc may find JavaScript.
+ * Each template contains a series of strings, which are written to files or inferred as options.
+ * The first source file implies a corresponding output file which should not be written
+ * if the comment contains JavaScript and JavaScript is not allowed.
+ */
+ enum Template {
+ OVR("<html><body> overview #COMMENT </body></html>", "package p; public class C { }"),
+ PKGINFO("#COMMENT package p;", "package p; public class C { }"),
+ PKGHTML("<html><body>#COMMENT package p;</body></html>", "package p; public class C { }"),
+ CLS("package p; #COMMENT public class C { }"),
+ CON("package p; public class C { #COMMENT public C() { } }"),
+ FLD("package p; public class C { #COMMENT public int f; }"),
+ MTH("package p; public class C { #COMMENT public void m() { } }"),
+ TOP("-top", "lorem #COMMENT ipsum", "package p; public class C { }"),
+ HDR("-header", "lorem #COMMENT ipsum", "package p; public class C { }"),
+ FTR("-footer", "lorem #COMMENT ipsum", "package p; public class C { }"),
+ BTM("-bottom", "lorem #COMMENT ipsum", "package p; public class C { }"),
+ DTTL("-doctitle", "lorem #COMMENT ipsum", "package p; public class C { }"),
+ PHDR("-packagesheader", "lorem #COMMENT ipsum", "package p; public class C { }");
+
+ Template(String... args) {
+ opts = new ArrayList<String>();
+ sources = new ArrayList<String>();
+ int i = 0;
+ while (args[i].startsWith("-")) {
+ // all options being tested have a single argument that follow the option
+ opts.add(args[i++]);
+ opts.add(args[i++]);
+ }
+ while(i < args.length) {
+ sources.add(args[i++]);
+ }
+ }
+
+ // groups: 1 <html> or not; 2: package name; 3: class name
+ private final Pattern pat =
+ Pattern.compile("(?i)(<html>)?.*?(?:package ([a-z]+);.*?(?:class ([a-z]+).*)?)?");
+
+ /**
+ * Infer the file in which to write the given source.
+ * @param dir the base source directory
+ * @param src the source text
+ * @return the file in which the source should be written
+ */
+ File getSrcFile(File srcDir, String src) {
+ String f;
+ Matcher m = pat.matcher(src);
+ if (!m.matches())
+ throw new Error("match failed");
+ if (m.group(3) != null) {
+ f = m.group(2) + "/" + m.group(3) + ".java";
+ } else if (m.group(2) != null) {
+ f = m.group(2) + "/" + (m.group(1) == null ? "package-info.java" : "package.html");
+ } else {
+ f = "overview.html";
+ }
+ return new File(srcDir, f);
+ }
+
+ /**
+ * Get the options to give to javadoc.
+ * @param srcDir the srcDir to use -overview is needed
+ * @return
+ */
+ List<String> getOpts(File srcDir) {
+ if (!opts.isEmpty()) {
+ return opts;
+ } else if (sources.get(0).contains("overview")) {
+ return Arrays.asList("-overview", getSrcFile(srcDir, sources.get(0)).getPath());
+ } else {
+ return Collections.emptyList();
+ }
+ }
+
+ /**
+ * Gets the output file corresponding to the first source file.
+ * This file should not be written if the comment contains JavaScript and JavaScripot is
+ * not allowed.
+ * @param dir the base output directory
+ * @return the output file
+ */
+ File getOutFile(File outDir) {
+ String f;
+ Matcher m = pat.matcher(sources.get(0));
+ if (!m.matches())
+ throw new Error("match failed");
+ if (m.group(3) != null) {
+ f = m.group(2) + "/" + m.group(3) + ".html";
+ } else if (m.group(2) != null) {
+ f = m.group(2) + "/package-summary.html";
+ } else {
+ f = "overview-summary.html";
+ }
+ return new File(outDir, f);
+ }
+
+ final List<String> opts;
+ final List<String> sources;
+ };
+
+ enum Option {
+ OFF(null),
+ ON("--allow-script-in-comments");
+
+ Option(String text) {
+ this.text = text;
+ }
+
+ final String text;
+ };
+
+ private PrintStream out = System.err;
+
+ public void run() throws Exception {
+ int count = 0;
+ for (Template template: Template.values()) {
+ for (Comment comment: Comment.values()) {
+ for (Option option: Option.values()) {
+ if (test(template, comment, option)) {
+ count++;
+ }
+ }
+ }
+ }
+
+ out.println(count + " test cases run");
+ if (errors > 0) {
+ throw new Exception(errors + " errors occurred");
+ }
+ }
+
+ boolean test(Template template, Comment comment, Option option) throws IOException {
+ if (option == Option.ON && !comment.hasScript) {
+ // skip --allowScriptInComments if comment does not contain JavaScript
+ return false;
+ }
+
+ String test = template + "-" + comment + "-" + option;
+ out.println("Test: " + test);
+
+ File dir = new File(test);
+ dir.mkdirs();
+ File srcDir = new File(dir, "src");
+ File outDir = new File(dir, "out");
+
+ String alert = "alert(\"" + test + "\");";
+ for (String src: template.sources) {
+ writeFile(template.getSrcFile(srcDir, src),
+ src.replace("#COMMENT",
+ "/** " + comment.text.replace("#ALERT", alert) + " **/"));
+ }
+
+ List<String> opts = new ArrayList<String>();
+ opts.add("-sourcepath");
+ opts.add(srcDir.getPath());
+ opts.add("-d");
+ opts.add(outDir.getPath());
+ if (option.text != null)
+ opts.add(option.text);
+ for (String opt: template.getOpts(srcDir)) {
+ opts.add(opt.replace("#COMMENT", comment.text.replace("#ALERT", alert)));
+ }
+ opts.addAll(comment.opts);
+ opts.add("-noindex"); // index not required; save time/space writing files
+ opts.add("p");
+
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ int rc = javadoc(opts, pw);
+ pw.close();
+ String log = sw.toString();
+ writeFile(new File(dir, "log.txt"), log);
+
+ out.println("opts: " + opts);
+ out.println(" rc: " + rc);
+ out.println(" log:");
+ out.println(log);
+
+ String ERROR = "Use --allow-script-in-comment";
+ File outFile = template.getOutFile(outDir);
+
+ boolean expectErrors = comment.hasScript && (option == Option.OFF);
+
+ if (expectErrors) {
+ check(rc != 0, "unexpected exit code: " + rc);
+ check(log.contains(ERROR), "expected error message not found");
+ check(!outFile.exists(), "output file found unexpectedly");
+ } else {
+ check(rc == 0, "unexpected exit code: " + rc);
+ check(!log.contains(ERROR), "error message found");
+ check(outFile.exists(), "output file not found");
+ }
+
+ out.println();
+ return true;
+ }
+
+ int javadoc(List<String> opts, PrintWriter pw) {
+ return com.sun.tools.javadoc.Main.execute("javadoc", pw, pw, pw,
+ "com.sun.tools.doclets.standard.Standard", opts.toArray(new String[opts.size()]));
+ }
+
+ File writeFile(File f, String text) throws IOException {
+ f.getParentFile().mkdirs();
+ FileWriter fw = new FileWriter(f);
+ try {
+ fw.write(text);
+ } finally {
+ fw.close();
+ }
+ return f;
+ }
+
+ void check(boolean cond, String errMessage) {
+ if (!cond) {
+ error(errMessage);
+ }
+ }
+
+ void error(String message) {
+ out.println("Error: " + message);
+ errors++;
+ }
+
+ int errors = 0;
+}
+