# HG changeset patch # User aefimov # Date 1468875192 -10800 # Node ID 6f0746b6de9fda350723daa17a4dee35c00a15eb # Parent 7b6c1bfeeb03ce40e986f4f55f1b3cca175eaa6f 8138725: Add options for Javadoc generation Reviewed-by: jjg diff -r 7b6c1bfeeb03 -r 6f0746b6de9f src/share/classes/com/sun/tools/doclets/formats/html/ConfigurationImpl.java --- a/src/share/classes/com/sun/tools/doclets/formats/html/ConfigurationImpl.java Tue Jul 12 14:52:08 2016 -0700 +++ b/src/share/classes/com/sun/tools/doclets/formats/html/ConfigurationImpl.java Mon Jul 18 23:53:12 2016 +0300 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 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 @@ -38,6 +38,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.JavaScriptScanner; import com.sun.tools.javadoc.RootDocImpl; /** @@ -181,6 +182,11 @@ public Set 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; @@ -283,8 +289,11 @@ doclintOpts.add(null); } else if (opt.startsWith("-xdoclint:")) { doclintOpts.add(opt.substring(opt.indexOf(":") + 1)); + } else if (opt.equals("--allow-script-in-comments")) { + allowScriptInComments = true; } } + if (root.specifiedClasses().length > 0) { Map map = new HashMap(); PackageDoc pd; @@ -301,9 +310,30 @@ if (root instanceof RootDocImpl) { ((RootDocImpl) root).initDocLint(doclintOpts, tagletManager.getCustomTagNames()); + 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 @@ -337,7 +367,8 @@ option.equals("-nonavbar") || option.equals("-nooverview") || option.equals("-xdoclint") || - option.startsWith("-xdoclint:")) { + option.startsWith("-xdoclint:") || + option.equals("--allow-script-in-comments")) { return 1; } else if (option.equals("-help")) { // Uugh: first, this should not be hidden inside optionLength, @@ -595,4 +626,13 @@ public Content newContent() { return new ContentBuilder(); } + + /** + * 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; + } } diff -r 7b6c1bfeeb03 -r 6f0746b6de9f src/share/classes/com/sun/tools/doclets/formats/html/HtmlDoclet.java --- a/src/share/classes/com/sun/tools/doclets/formats/html/HtmlDoclet.java Tue Jul 12 14:52:08 2016 -0700 +++ b/src/share/classes/com/sun/tools/doclets/formats/html/HtmlDoclet.java Mon Jul 18 23:53:12 2016 +0300 @@ -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 @@ -190,6 +190,8 @@ } } catch (IOException e) { throw new DocletAbortException(e); + } catch (FatalError fe) { + throw fe; } catch (DocletAbortException de) { throw de; } catch (Exception e) { diff -r 7b6c1bfeeb03 -r 6f0746b6de9f src/share/classes/com/sun/tools/doclets/formats/html/markup/HtmlWriter.java --- a/src/share/classes/com/sun/tools/doclets/formats/html/markup/HtmlWriter.java Tue Jul 12 14:52:08 2016 -0700 +++ b/src/share/classes/com/sun/tools/doclets/formats/html/markup/HtmlWriter.java Mon Jul 18 23:53:12 2016 +0300 @@ -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 @@ -164,7 +164,9 @@ public final Content descfrmInterfaceLabel; - private final Writer writer; + private final DocFile file; + + private Writer writer; private Content script; @@ -180,7 +182,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; profileTableHeader = new String[] { @@ -239,6 +241,7 @@ } public void write(Content c) throws IOException { + writer = file.openWriter(); c.write(writer, true); } diff -r 7b6c1bfeeb03 -r 6f0746b6de9f src/share/classes/com/sun/tools/doclets/internal/toolkit/AbstractDoclet.java --- a/src/share/classes/com/sun/tools/doclets/internal/toolkit/AbstractDoclet.java Tue Jul 12 14:52:08 2016 -0700 +++ b/src/share/classes/com/sun/tools/doclets/internal/toolkit/AbstractDoclet.java Mon Jul 18 23:53:12 2016 +0300 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 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 @@ -83,6 +83,8 @@ } catch (Configuration.Fault f) { root.printError(f.getMessage()); return false; + } catch (FatalError fe) { + return false; } catch (DocletAbortException e) { Throwable cause = e.getCause(); if (cause != null) { diff -r 7b6c1bfeeb03 -r 6f0746b6de9f src/share/classes/com/sun/tools/doclets/internal/toolkit/builders/AbstractBuilder.java --- a/src/share/classes/com/sun/tools/doclets/internal/toolkit/builders/AbstractBuilder.java Tue Jul 12 14:52:08 2016 -0700 +++ b/src/share/classes/com/sun/tools/doclets/internal/toolkit/builders/AbstractBuilder.java Mon Jul 18 23:53:12 2016 +0300 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 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 @@ -140,7 +140,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 " + diff -r 7b6c1bfeeb03 -r 6f0746b6de9f src/share/classes/com/sun/tools/doclets/internal/toolkit/resources/doclets.properties --- a/src/share/classes/com/sun/tools/doclets/internal/toolkit/resources/doclets.properties Tue Jul 12 14:52:08 2016 -0700 +++ b/src/share/classes/com/sun/tools/doclets/internal/toolkit/resources/doclets.properties Mon Jul 18 23:53:12 2016 +0300 @@ -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.Profiles=Profiles diff -r 7b6c1bfeeb03 -r 6f0746b6de9f src/share/classes/com/sun/tools/doclets/internal/toolkit/util/FatalError.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/share/classes/com/sun/tools/doclets/internal/toolkit/util/FatalError.java Mon Jul 18 23:53:12 2016 +0300 @@ -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; + +/** + *

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. + */ +@Deprecated +public class FatalError extends Error { + private static final long serialVersionUID = -9131058909576418984L; + + public FatalError() { } +} diff -r 7b6c1bfeeb03 -r 6f0746b6de9f src/share/classes/com/sun/tools/doclint/Checker.java --- a/src/share/classes/com/sun/tools/doclint/Checker.java Tue Jul 12 14:52:08 2016 -0700 +++ b/src/share/classes/com/sun/tools/doclint/Checker.java Mon Jul 18 23:53:12 2016 +0300 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + * 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 @@ -126,7 +126,7 @@ } } - private Deque tagStack; // TODO: maybe want to record starting tree as well + private final Deque tagStack; // TODO: maybe want to record starting tree as well private HtmlTag currHeaderTag; private final int implicitHeaderLevel; @@ -401,7 +401,16 @@ break; case OTHER: - env.messages.error(HTML, tree, "dc.tag.not.allowed", treeName); + switch (t) { + case SCRIPT: + // - ^ OtherTagsTest.java:22: error: element not allowed in documentation comments: * <title> ^ -9 errors +8 errors diff -r 7b6c1bfeeb03 -r 6f0746b6de9f test/tools/javadoc/TestScriptInComment.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/tools/javadoc/TestScriptInComment.java Mon Jul 18 23:53:12 2016 +0300 @@ -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 + * @run main TestScriptInComment + */ + +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("", true), // script tag in Lower Case + UC("", true), // script tag in Upper Case + WS("< script >#ALERT", false, "-Xdoclint:none"), // script tag with invalid white space + SA("", true), // script tag with an attribute + ON("x", true), // event handler attribute + URI("x", 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 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(" overview #COMMENT ", "package p; public class C { }"), + PKGINFO("#COMMENT package p;", "package p; public class C { }"), + PKGHTML("#COMMENT package p;", "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(); + sources = new ArrayList(); + 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 or not; 2: package name; 3: class name + private final Pattern pat = + Pattern.compile("(?i)()?.*?(?: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 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 opts; + final List 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 opts = new ArrayList(); + 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 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; +} +