1 /*
   2  * Copyright (c) 2006, 2015, 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 /*
  27  * @test
  28  * @bug 8003967
  29  * @summary detect and remove all mutable implicit static enum fields in langtools
  30  * @modules jdk.jdeps/com.sun.tools.classfile
  31  *          jdk.compiler/com.sun.tools.javac.util
  32  * @run main DetectMutableStaticFields
  33  */
  34 
  35 import java.io.File;
  36 import java.io.IOException;
  37 import java.net.URI;
  38 import java.net.URISyntaxException;
  39 import java.util.ArrayList;
  40 import java.util.Arrays;
  41 import java.util.EnumSet;
  42 import java.util.HashMap;
  43 import java.util.List;
  44 import java.util.Map;
  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 import com.sun.tools.classfile.ClassFile;
  52 import com.sun.tools.classfile.ConstantPoolException;
  53 import com.sun.tools.classfile.Descriptor;
  54 import com.sun.tools.classfile.Descriptor.InvalidDescriptor;
  55 import com.sun.tools.classfile.Field;
  56 
  57 import static javax.tools.JavaFileObject.Kind.CLASS;
  58 import static com.sun.tools.classfile.AccessFlags.ACC_ENUM;
  59 import static com.sun.tools.classfile.AccessFlags.ACC_FINAL;
  60 import static com.sun.tools.classfile.AccessFlags.ACC_STATIC;
  61 
  62 public class DetectMutableStaticFields {
  63 
  64     private static final String keyResource =
  65             "com/sun/tools/javac/tree/JCTree.class";
  66 
  67     private String[] packagesToSeekFor = new String[] {
  68         "javax.tools",
  69         "javax.lang.model",
  70         "com.sun.javadoc",
  71         "com.sun.source",
  72         "com.sun.tools.classfile",
  73         "com.sun.tools.doclets",
  74         "com.sun.tools.javac",
  75         "com.sun.tools.javadoc",
  76         "com.sun.tools.javah",
  77         "com.sun.tools.javap",
  78     };
  79 
  80     private static final Map<String, List<String>> classFieldsToIgnoreMap = new HashMap<>();
  81 
  82     static {
  83         classFieldsToIgnoreMap.
  84                 put("javax/tools/ToolProvider",
  85                     Arrays.asList("instance"));
  86         classFieldsToIgnoreMap.
  87                 put("com/sun/tools/javah/JavahTask",
  88                     Arrays.asList("versionRB"));
  89         classFieldsToIgnoreMap.
  90                 put("com/sun/tools/classfile/Dependencies$DefaultFilter",
  91                     Arrays.asList("instance"));
  92         classFieldsToIgnoreMap.
  93                 put("com/sun/tools/javap/JavapTask",
  94                     Arrays.asList("versionRB"));
  95         classFieldsToIgnoreMap.
  96                 put("com/sun/tools/doclets/formats/html/HtmlDoclet",
  97                     Arrays.asList("docletToStart"));
  98         classFieldsToIgnoreMap.
  99                 put("com/sun/tools/javac/util/JCDiagnostic",
 100                     Arrays.asList("fragmentFormatter"));
 101         classFieldsToIgnoreMap.
 102                 put("com/sun/tools/javac/util/JavacMessages",
 103                     Arrays.asList("defaultBundle", "defaultMessages"));
 104         classFieldsToIgnoreMap.
 105                 put("com/sun/tools/javac/file/ZipFileIndexCache",
 106                     Arrays.asList("sharedInstance"));
 107         classFieldsToIgnoreMap.
 108                 put("com/sun/tools/javac/file/JRTIndex",
 109                     Arrays.asList("sharedInstance"));
 110         classFieldsToIgnoreMap.
 111                 put("com/sun/tools/javac/main/JavaCompiler",
 112                     Arrays.asList("versionRB"));
 113         classFieldsToIgnoreMap.
 114                 put("com/sun/tools/javac/code/Type",
 115                     Arrays.asList("moreInfo"));
 116         classFieldsToIgnoreMap.
 117                 put("com/sun/tools/javac/util/SharedNameTable",
 118                     Arrays.asList("freelist"));
 119         classFieldsToIgnoreMap.
 120                 put("com/sun/tools/javac/util/Log",
 121                     Arrays.asList("useRawMessages"));
 122     }
 123 
 124     private List<String> errors = new ArrayList<>();
 125 
 126     public static void main(String[] args) {
 127         try {
 128             new DetectMutableStaticFields().run();
 129         } catch (Exception ex) {
 130             throw new AssertionError(
 131                     "Exception during test execution with cause ",
 132                     ex.getCause());
 133         }
 134     }
 135 
 136     private void run()
 137         throws
 138             IOException,
 139             ConstantPoolException,
 140             InvalidDescriptor,
 141             URISyntaxException {
 142 
 143         URI resource = findResource(keyResource);
 144         if (resource == null) {
 145             throw new AssertionError("Resource " + keyResource +
 146                 "not found in the class path");
 147         }
 148         analyzeResource(resource);
 149 
 150         if (errors.size() > 0) {
 151             for (String error: errors) {
 152                 System.err.println(error);
 153             }
 154             throw new AssertionError("There are mutable fields, "
 155                 + "please check output");
 156         }
 157     }
 158 
 159     URI findResource(String className) throws URISyntaxException {
 160         URI uri = getClass().getClassLoader().getResource(className).toURI();
 161         if (uri.getScheme().equals("jar")) {
 162             String ssp = uri.getRawSchemeSpecificPart();
 163             int sep = ssp.lastIndexOf("!");
 164             uri = new URI(ssp.substring(0, sep));
 165         } else if (uri.getScheme().equals("file")) {
 166             uri = new URI(uri.getPath().substring(0,
 167                     uri.getPath().length() - keyResource.length()));
 168         }
 169         return uri;
 170     }
 171 
 172     boolean shouldAnalyzePackage(String packageName) {
 173         for (String aPackage: packagesToSeekFor) {
 174             if (packageName.contains(aPackage)) {
 175                 return true;
 176             }
 177         }
 178         return false;
 179     }
 180 
 181     void analyzeResource(URI resource)
 182         throws
 183             IOException,
 184             ConstantPoolException,
 185             InvalidDescriptor {
 186         JavaCompiler tool = ToolProvider.getSystemJavaCompiler();
 187         try (StandardJavaFileManager fm = tool.getStandardFileManager(null, null, null)) {
 188             JavaFileManager.Location location =
 189                     StandardLocation.locationFor(resource.getPath());
 190             fm.setLocation(location, com.sun.tools.javac.util.List.of(
 191                     new File(resource.getPath())));
 192 
 193             for (JavaFileObject file : fm.list(location, "", EnumSet.of(CLASS), true)) {
 194                 String className = fm.inferBinaryName(location, file);
 195                 int index = className.lastIndexOf('.');
 196                 String pckName = index == -1 ? "" : className.substring(0, index);
 197                 if (shouldAnalyzePackage(pckName)) {
 198                     analyzeClassFile(ClassFile.read(file.openInputStream()));
 199                 }
 200             }
 201         }
 202     }
 203 
 204     List<String> currentFieldsToIgnore;
 205 
 206     boolean ignoreField(String field) {
 207         if (currentFieldsToIgnore != null) {
 208             for (String fieldToIgnore : currentFieldsToIgnore) {
 209                 if (field.equals(fieldToIgnore)) {
 210                     return true;
 211                 }
 212             }
 213         }
 214         return false;
 215     }
 216 
 217     void analyzeClassFile(ClassFile classFileToCheck)
 218         throws
 219             IOException,
 220             ConstantPoolException,
 221             Descriptor.InvalidDescriptor {
 222         boolean enumClass =
 223                 (classFileToCheck.access_flags.flags & ACC_ENUM) != 0;
 224         boolean nonFinalStaticEnumField;
 225         boolean nonFinalStaticField;
 226 
 227         currentFieldsToIgnore =
 228                 classFieldsToIgnoreMap.get(classFileToCheck.getName());
 229 
 230         for (Field field : classFileToCheck.fields) {
 231             if (ignoreField(field.getName(classFileToCheck.constant_pool))) {
 232                 continue;
 233             }
 234             nonFinalStaticEnumField =
 235                     (field.access_flags.flags & (ACC_ENUM | ACC_FINAL)) == 0;
 236             nonFinalStaticField =
 237                     (field.access_flags.flags & ACC_STATIC) != 0 &&
 238                     (field.access_flags.flags & ACC_FINAL) == 0;
 239             if (enumClass ? nonFinalStaticEnumField : nonFinalStaticField) {
 240                 errors.add("There is a mutable field named " +
 241                         field.getName(classFileToCheck.constant_pool) +
 242                         ", at class " +
 243                         classFileToCheck.getName());
 244             }
 245         }
 246     }
 247 
 248 }