8010304: javac should detect all mutable implicit static fields in langtools using a plugin
authorvromero
Fri, 26 Apr 2013 15:59:39 +0100
changeset 17283 cddfdf252f4a
parent 17282 c6964ad7df63
child 17284 7cae365bf8d5
8010304: javac should detect all mutable implicit static fields in langtools using a plugin Reviewed-by: jjg
langtools/make/build.xml
langtools/make/tools/crules/AbstractCodingRulesAnalyzer.java
langtools/make/tools/crules/MutableFieldsAnalyzer.java
langtools/make/tools/crules/resources/crules.properties
--- a/langtools/make/build.xml	Fri Apr 26 10:17:01 2013 +0100
+++ b/langtools/make/build.xml	Fri Apr 26 15:59:39 2013 +0100
@@ -717,6 +717,29 @@
     <target name="sjavac" depends="build-sjavac,jtreg-sjavac,findbugs-sjavac"/>
 
     <!--
+    **** crules targets.
+    -->
+
+    <target name="build-crules" depends="-def-compilecrules,-def-build-jar-with-services">
+        <compilecrules/>
+        <build-jar-with-services
+                    name="crules"
+                    includes="crules/* crules/resources/*"
+                    classes.dir="${build.toolclasses.dir}"
+                    lib.dir="${build.toolclasses.dir}"
+                    jarmainclass=""
+                    jarclasspath="crules.jar"
+                    service.type="com.sun.source.util.Plugin"
+                    service.provider="crules.MutableFieldsAnalyzer"/>
+        <build-tool name="crules"/>
+    </target>
+
+    <target name="check-coding-rules" depends="build-bootstrap-javac,-create-import-jdk-stubs,build-crules">
+        <build-classes includes="${javac.includes}"
+            plugin.options="-J-Xbootclasspath/a:${build.toolclasses.dir}/crules.jar -Xplugin:mutable_fields_analyzer" />
+    </target>
+
+    <!--
     **** Create import JDK stubs.
     -->
 
@@ -811,6 +834,31 @@
         </macrodef>
     </target>
 
+    <target name="-def-build-jar-with-services">
+        <macrodef name="build-jar-with-services">
+            <attribute name="name"/>
+            <attribute name="includes"/>
+            <attribute name="classes.dir" default="${build.classes.dir}"/>
+            <attribute name="lib.dir" default="${dist.lib.dir}"/>
+            <attribute name="jarmainclass" default="com.sun.tools.@{name}.Main"/>
+            <attribute name="jarclasspath" default=""/>
+            <attribute name="service.type" default=""/>
+            <attribute name="service.provider" default=""/>
+            <sequential>
+                <mkdir dir="${build.toolclasses.dir}"/>
+                <jar destfile="@{lib.dir}/@{name}.jar"
+                     basedir="@{classes.dir}"
+                     includes="@{includes}">
+                    <service type="@{service.type}" provider="@{service.provider}"/>
+                    <manifest>
+                        <attribute name="Main-Class" value="@{jarmainclass}"/>
+                        <attribute name="Class-Path" value="@{jarclasspath}"/>
+                    </manifest>
+                </jar>
+            </sequential>
+        </macrodef>
+    </target>
+
     <target name="-def-build-classes" depends="-def-pcompile">
         <macrodef name="build-classes">
             <attribute name="includes"/>
@@ -826,6 +874,7 @@
             <attribute name="target" default="${javac.target}"/>
             <attribute name="release" default="${release}"/>
             <attribute name="full.version" default="${full.version}"/>
+            <attribute name="plugin.options" default=""/>
             <sequential>
                 <echo level="verbose" message="build-classes: excludes=@{excludes}"/>
                 <echo level="verbose" message="build-classes: bootclasspath.opt=@{bootclasspath.opt}"/>
@@ -868,6 +917,7 @@
                     <compilerarg line="${javac.no.jdk.warnings}"/>
                     <compilerarg line="${javac.version.opt}"/>
                     <compilerarg line="${javac.lint.opts}"/>
+                    <compilerarg line="@{plugin.options}"/>
                 </javac>
                 <copy todir="@{classes.dir}" includeemptydirs="false">
                     <fileset dir="${src.classes.dir}" includes="@{includes}" excludes="@{excludes}">
@@ -935,6 +985,32 @@
                  classpath="${build.toolclasses.dir}/"/>
     </target>
 
+    <target name="-def-compilecrules">
+        <macrodef name="compilecrules">
+            <sequential>
+                <mkdir dir="${build.toolclasses.dir}"/>
+                <javac fork="true"
+                       source="${boot.javac.source}"
+                       target="${boot.javac.target}"
+                       executable="${boot.java.home}/bin/javac"
+                       srcdir="${make.tools.dir}"
+                       includes="crules/*"
+                       destdir="${build.toolclasses.dir}/"
+                       classpath="${ant.core.lib}"
+                       bootclasspath="${boot.java.home}/jre/lib/rt.jar"
+                       includeantruntime="false">
+                    <compilerarg value="-Xbootclasspath/p:${build.bootstrap.dir}/classes"/>
+                    <compilerarg line="${javac.lint.opts}"/>
+                </javac>
+                <copy todir="${build.toolclasses.dir}/" includeemptydirs="false">
+                    <fileset dir="${make.tools.dir}">
+                        <include name="**/*.properties"/>
+                    </fileset>
+                </copy>
+            </sequential>
+        </macrodef>
+    </target>
+
     <target name="-def-genstubs" depends="build-bootstrap-javac" if="require.import.jdk.stubs">
         <mkdir dir="${build.toolclasses.dir}"/>
         <javac fork="true"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/make/tools/crules/AbstractCodingRulesAnalyzer.java	Fri Apr 26 15:59:39 2013 +0100
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package crules;
+
+import java.text.MessageFormat;
+import java.util.Locale;
+import java.util.ResourceBundle;
+
+import javax.lang.model.element.TypeElement;
+import javax.tools.JavaFileObject;
+
+import com.sun.source.tree.Tree;
+import com.sun.source.util.JavacTask;
+import com.sun.source.util.Plugin;
+import com.sun.source.util.TaskEvent;
+import com.sun.source.util.TaskListener;
+import com.sun.source.util.Trees;
+import com.sun.tools.javac.api.BasicJavacTask;
+import com.sun.tools.javac.tree.JCTree;
+import com.sun.tools.javac.tree.TreeScanner;
+import com.sun.tools.javac.util.Context;
+import com.sun.tools.javac.util.Log;
+
+import static com.sun.source.util.TaskEvent.Kind;
+
+public abstract class AbstractCodingRulesAnalyzer implements Plugin {
+
+    protected Log log;
+    protected Trees trees;
+    protected TreeScanner treeVisitor;
+    protected Kind eventKind;
+    protected Messages messages;
+
+    public void init(JavacTask task, String... args) {
+        BasicJavacTask impl = (BasicJavacTask)task;
+        Context context = impl.getContext();
+        log = Log.instance(context);
+        trees = Trees.instance(task);
+        messages = new Messages();
+        task.addTaskListener(new PostAnalyzeTaskListener());
+    }
+
+    public class PostAnalyzeTaskListener implements TaskListener {
+
+        @Override
+        public void started(TaskEvent taskEvent) {}
+
+        @Override
+        public void finished(TaskEvent taskEvent) {
+            if (taskEvent.getKind().equals(eventKind)) {
+                TypeElement typeElem = taskEvent.getTypeElement();
+                Tree tree = trees.getTree(typeElem);
+                if (tree != null) {
+                    JavaFileObject prevSource = log.currentSourceFile();
+                    try {
+                        log.useSource(taskEvent.getCompilationUnit().getSourceFile());
+                        treeVisitor.scan((JCTree)tree);
+                    } finally {
+                        log.useSource(prevSource);
+                    }
+                }
+            }
+        }
+    }
+
+    class Messages {
+        ResourceBundle bundle;
+
+        Messages() {
+            String name = getClass().getPackage().getName() + ".resources.crules";
+            bundle = ResourceBundle.getBundle(name, Locale.ENGLISH);
+        }
+
+        public void error(JCTree tree, String code, Object... args) {
+            String msg = (code == null) ? (String) args[0] : localize(code, args);
+            log.error(tree, "proc.messager", msg.toString());
+        }
+
+        private String localize(String code, Object... args) {
+            String msg = bundle.getString(code);
+            if (msg == null) {
+                StringBuilder sb = new StringBuilder();
+                sb.append("message file broken: code=").append(code);
+                if (args.length > 0) {
+                    sb.append(" arguments={0}");
+                    for (int i = 1; i < args.length; i++) {
+                        sb.append(", {").append(i).append("}");
+                    }
+                }
+                msg = sb.toString();
+            }
+            return MessageFormat.format(msg, args);
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/make/tools/crules/MutableFieldsAnalyzer.java	Fri Apr 26 15:59:39 2013 +0100
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package crules;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.sun.tools.javac.code.Kinds;
+import com.sun.tools.javac.tree.TreeScanner;
+
+import static com.sun.source.util.TaskEvent.Kind;
+import static com.sun.tools.javac.code.Flags.*;
+import static com.sun.tools.javac.tree.JCTree.JCVariableDecl;
+
+public class MutableFieldsAnalyzer extends AbstractCodingRulesAnalyzer {
+
+    public MutableFieldsAnalyzer() {
+        treeVisitor = new MutableFieldsVisitor();
+        eventKind = Kind.ANALYZE;
+    }
+
+    public String getName() {
+        return "mutable_fields_analyzer";
+    }
+
+    private boolean ignoreField(String className, String field) {
+        List<String> currentFieldsToIgnore =
+                classFieldsToIgnoreMap.get(className);
+        if (currentFieldsToIgnore != null) {
+            for (String fieldToIgnore : currentFieldsToIgnore) {
+                if (field.equals(fieldToIgnore)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    class MutableFieldsVisitor extends TreeScanner {
+
+        @Override
+        public void visitVarDef(JCVariableDecl tree) {
+            boolean isJavacPack = tree.sym.outermostClass().fullname.toString()
+                    .contains(packageToCheck);
+            if (isJavacPack &&
+                (tree.sym.flags() & SYNTHETIC) == 0 &&
+                tree.sym.owner.kind == Kinds.TYP) {
+                if (!ignoreField(tree.sym.owner.flatName().toString(),
+                        tree.getName().toString())) {
+                    boolean enumClass = (tree.sym.owner.flags() & ENUM) != 0;
+                    boolean nonFinalStaticEnumField =
+                            (tree.sym.flags() & (ENUM | FINAL)) == 0;
+                    boolean nonFinalStaticField =
+                            (tree.sym.flags() & STATIC) != 0 &&
+                            (tree.sym.flags() & FINAL) == 0;
+                    if (enumClass ? nonFinalStaticEnumField : nonFinalStaticField) {
+                        messages.error(tree, "crules.err.var.must.be.final", tree);
+                    }
+                }
+            }
+            super.visitVarDef(tree);
+        }
+
+    }
+
+    private static final String packageToCheck = "com.sun.tools.javac";
+
+    private static final Map<String, List<String>> classFieldsToIgnoreMap =
+                new HashMap<String, List<String>>();
+
+    static {
+        classFieldsToIgnoreMap.
+                put("com.sun.tools.javac.util.JCDiagnostic",
+                    Arrays.asList("fragmentFormatter"));
+        classFieldsToIgnoreMap.
+                put("com.sun.tools.javac.util.JavacMessages",
+                    Arrays.asList("defaultBundle", "defaultMessages"));
+        classFieldsToIgnoreMap.
+                put("com.sun.tools.javac.file.ZipFileIndexCache",
+                    Arrays.asList("sharedInstance"));
+        classFieldsToIgnoreMap.
+                put("com.sun.tools.javac.main.JavaCompiler",
+                    Arrays.asList("versionRB"));
+        classFieldsToIgnoreMap.
+                put("com.sun.tools.javac.code.Type",
+                    Arrays.asList("moreInfo"));
+        classFieldsToIgnoreMap.
+                put("com.sun.tools.javac.util.SharedNameTable",
+                    Arrays.asList("freelist"));
+        classFieldsToIgnoreMap.
+                put("com.sun.tools.javac.util.Log",
+                    Arrays.asList("useRawMessages"));
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/make/tools/crules/resources/crules.properties	Fri Apr 26 15:59:39 2013 +0100
@@ -0,0 +1,28 @@
+#
+# Copyright (c) 2013, 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.
+#
+
+# 0: symbol
+crules.err.var.must.be.final=\
+    Static variable {0} must be final