1 /*
   2  * Copyright (c) 2006, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  * @test
  26  * @bug 8003967
  27  * @summary detect and remove all mutable implicit static enum fields in langtools
  28  * @modules jdk.jdeps/com.sun.tools.classfile
  29  *          jdk.compiler/com.sun.tools.javac.util
  30  * @run main DetectMutableStaticFields
  31  */
  32 
  33 import java.io.File;
  34 import java.io.IOException;
  35 import java.net.URI;
  36 import java.net.URISyntaxException;
  37 import java.util.ArrayList;
  38 import java.util.Arrays;
  39 import java.util.EnumSet;
  40 import java.util.HashMap;
  41 import java.util.List;
  42 import java.util.Map;
  43 import java.util.Set;
  44 
  45 import javax.tools.JavaCompiler;
  46 import javax.tools.JavaFileManager;
  47 import javax.tools.JavaFileObject;
  48 import javax.tools.StandardJavaFileManager;
  49 import javax.tools.StandardLocation;
  50 import javax.tools.ToolProvider;
  51 
  52 import com.sun.tools.classfile.ClassFile;
  53 import com.sun.tools.classfile.ConstantPoolException;
  54 import com.sun.tools.classfile.Descriptor;
  55 import com.sun.tools.classfile.Descriptor.InvalidDescriptor;
  56 import com.sun.tools.classfile.Field;
  57 
  58 import static javax.tools.JavaFileObject.Kind.CLASS;
  59 
  60 import static com.sun.tools.classfile.AccessFlags.ACC_ENUM;
  61 import static com.sun.tools.classfile.AccessFlags.ACC_FINAL;
  62 import static com.sun.tools.classfile.AccessFlags.ACC_STATIC;
  63 
  64 public class DetectMutableStaticFields {
  65 
  66     private final String[] modules = {
  67         "java.compiler",
  68         "jdk.compiler",
  69         "jdk.javadoc",
  70         "jdk.jdeps"
  71     };
  72 
  73     private final String[] packagesToSeekFor = new String[] {
  74         "javax.tools",
  75         "javax.lang.model",
  76         "com.sun.javadoc",
  77         "com.sun.source",
  78         "com.sun.tools.classfile",
  79         "com.sun.tools.doclets",
  80         "com.sun.tools.javac",
  81         "com.sun.tools.javadoc",
  82         "com.sun.tools.javah",
  83         "com.sun.tools.javap",
  84         "jdk.javadoc"
  85     };
  86 
  87     private static final Map<String, List<String>> classFieldsToIgnoreMap = new HashMap<>();
  88     private static void ignore(String className, String... fields) {
  89         classFieldsToIgnoreMap.put(className, Arrays.asList(fields));
  90     }
  91 
  92     static {
  93         ignore("javax/tools/ToolProvider", "instance");
  94         ignore("jdk/javadoc/internal/tool/Start", "versionRB");
  95         ignore("com/sun/tools/javah/JavahTask", "versionRB");
  96         ignore("com/sun/tools/classfile/Dependencies$DefaultFilter", "instance");
  97         ignore("com/sun/tools/javap/JavapTask", "versionRB");
  98         ignore("com/sun/tools/doclets/formats/html/HtmlDoclet", "docletToStart");
  99         ignore("com/sun/tools/javac/util/JCDiagnostic", "fragmentFormatter");
 100         ignore("com/sun/tools/javac/util/JavacMessages", "defaultBundle", "defaultMessages");
 101         ignore("com/sun/tools/javac/file/JRTIndex", "sharedInstance");
 102         ignore("com/sun/tools/javac/main/JavaCompiler", "versionRB");
 103         ignore("com/sun/tools/javac/code/Type", "moreInfo");
 104         ignore("com/sun/tools/javac/util/SharedNameTable", "freelist");
 105         ignore("com/sun/tools/javac/util/Log", "useRawMessages");
 106 
 107         // The following static fields are used for caches of information obtained
 108         // by reflective lookup, to avoid explicit references that are not available
 109         // when running javac on JDK 8.
 110         ignore("com/sun/tools/javac/util/JDK9Wrappers$Configuration",
 111                 "resolveAndBindMethod", "configurationClass");
 112         ignore("com/sun/tools/javac/util/JDK9Wrappers$Layer",
 113                 "bootMethod", "defineModulesWithOneLoaderMethod", "configurationMethod", "layerClass");
 114         ignore("com/sun/tools/javac/util/JDK9Wrappers$Module",
 115                 "addExportsMethod", "addUsesMethod", "getModuleMethod", "getUnnamedModuleMethod");
 116         ignore("com/sun/tools/javac/util/JDK9Wrappers$ModuleDescriptor$Version",
 117                 "versionClass", "parseMethod");
 118         ignore("com/sun/tools/javac/util/JDK9Wrappers$ModuleFinder",
 119                 "moduleFinderClass", "ofMethod");
 120         ignore("com/sun/tools/javac/util/JDK9Wrappers$ServiceLoaderHelper",
 121                 "loadMethod");
 122         ignore("com/sun/tools/javac/util/JDK9Wrappers$VMHelper",
 123                 "vmClass", "getRuntimeArgumentsMethod");
 124         ignore("com/sun/tools/javac/util/JDK9Wrappers$JmodFile",
 125                 "jmodFileClass", "checkMagicMethod");
 126     }
 127 
 128     private final List<String> errors = new ArrayList<>();
 129 
 130     public static void main(String[] args) {
 131         try {
 132             new DetectMutableStaticFields().run();
 133         } catch (Exception ex) {
 134             throw new AssertionError("Exception during test execution: " + ex, ex);
 135         }
 136     }
 137 
 138     private void run()
 139         throws
 140             IOException,
 141             ConstantPoolException,
 142             InvalidDescriptor,
 143             URISyntaxException {
 144 
 145         JavaCompiler tool = ToolProvider.getSystemJavaCompiler();
 146         try (StandardJavaFileManager fm = tool.getStandardFileManager(null, null, null)) {
 147             for (String module: modules) {
 148                 analyzeModule(fm, module);
 149             }
 150         }
 151 
 152         if (errors.size() > 0) {
 153             for (String error: errors) {
 154                 System.err.println(error);
 155             }
 156             throw new AssertionError("There are mutable fields, "
 157                 + "please check output");
 158         }
 159     }
 160 
 161     boolean shouldAnalyzePackage(String packageName) {
 162         for (String aPackage: packagesToSeekFor) {
 163             if (packageName.contains(aPackage)) {
 164                 return true;
 165             }
 166         }
 167         return false;
 168     }
 169 
 170     void analyzeModule(StandardJavaFileManager fm, String moduleName)
 171         throws
 172             IOException,
 173             ConstantPoolException,
 174             InvalidDescriptor {
 175         JavaFileManager.Location location =
 176                 fm.getLocationForModule(StandardLocation.SYSTEM_MODULES, moduleName);
 177         if (location == null)
 178             throw new AssertionError("can't find module " + moduleName);
 179 
 180         for (JavaFileObject file : fm.list(location, "", EnumSet.of(CLASS), true)) {
 181             String className = fm.inferBinaryName(location, file);
 182             int index = className.lastIndexOf('.');
 183             String pckName = index == -1 ? "" : className.substring(0, index);
 184             if (shouldAnalyzePackage(pckName)) {
 185                 analyzeClassFile(ClassFile.read(file.openInputStream()));
 186             }
 187         }
 188     }
 189 
 190     List<String> currentFieldsToIgnore;
 191 
 192     boolean ignoreField(String field) {
 193         if (currentFieldsToIgnore != null) {
 194             for (String fieldToIgnore : currentFieldsToIgnore) {
 195                 if (field.equals(fieldToIgnore)) {
 196                     return true;
 197                 }
 198             }
 199         }
 200         return false;
 201     }
 202 
 203     void analyzeClassFile(ClassFile classFileToCheck)
 204         throws
 205             IOException,
 206             ConstantPoolException,
 207             Descriptor.InvalidDescriptor {
 208         boolean enumClass =
 209                 (classFileToCheck.access_flags.flags & ACC_ENUM) != 0;
 210         boolean nonFinalStaticEnumField;
 211         boolean nonFinalStaticField;
 212 
 213         currentFieldsToIgnore =
 214                 classFieldsToIgnoreMap.get(classFileToCheck.getName());
 215 
 216         for (Field field : classFileToCheck.fields) {
 217             if (ignoreField(field.getName(classFileToCheck.constant_pool))) {
 218                 continue;
 219             }
 220             nonFinalStaticEnumField =
 221                     (field.access_flags.flags & (ACC_ENUM | ACC_FINAL)) == 0;
 222             nonFinalStaticField =
 223                     (field.access_flags.flags & ACC_STATIC) != 0 &&
 224                     (field.access_flags.flags & ACC_FINAL) == 0;
 225             if (enumClass ? nonFinalStaticEnumField : nonFinalStaticField) {
 226                 errors.add("There is a mutable field named " +
 227                         field.getName(classFileToCheck.constant_pool) +
 228                         ", at class " +
 229                         classFileToCheck.getName());
 230             }
 231         }
 232     }
 233 
 234 }