1 /* 2 * Copyright (c) 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 import com.sun.tools.classfile.*; 25 import com.sun.tools.jdeps.ClassFileReader; 26 import static com.sun.tools.classfile.ConstantPool.*; 27 import java.io.File; 28 import java.io.IOException; 29 import java.io.UncheckedIOException; 30 import java.net.URI; 31 import java.nio.file.FileSystem; 32 import java.nio.file.FileSystems; 33 import java.nio.file.Files; 34 import java.nio.file.Path; 35 import java.nio.file.Paths; 36 import java.util.ArrayList; 37 import java.util.HashSet; 38 import java.util.List; 39 import java.util.Set; 40 import java.util.concurrent.Callable; 41 import java.util.concurrent.ConcurrentSkipListSet; 42 import java.util.concurrent.ExecutionException; 43 import java.util.concurrent.ExecutorService; 44 import java.util.concurrent.Executors; 45 import java.util.concurrent.FutureTask; 46 import java.util.stream.Collectors; 47 import java.util.stream.Stream; 48 49 /* 50 * @test 51 * @summary CallerSensitive methods should be static or final instance 52 * methods except the known list of non-final instance methods 53 * @modules jdk.jdeps/com.sun.tools.classfile 54 * jdk.jdeps/com.sun.tools.jdeps 55 * @build CheckCSMs 56 * @run main/othervm/timeout=900 CheckCSMs 57 */ 58 public class CheckCSMs { 59 private static int numThreads = 3; 60 private static boolean listCSMs = false; 61 private final ExecutorService pool; 62 63 // The goal is to remove this list of Non-final instance @CS methods 64 // over time. Do not add any new one to this list. 65 private static Set<String> KNOWN_NON_FINAL_CSMS = 66 Set.of("java/io/ObjectStreamField#getType ()Ljava/lang/Class;", 67 "java/io/ObjectStreamClass#forClass ()Ljava/lang/Class;", 68 "java/lang/Runtime#load (Ljava/lang/String;)V", 69 "java/lang/Runtime#loadLibrary (Ljava/lang/String;)V", 70 "java/lang/Thread#getContextClassLoader ()Ljava/lang/ClassLoader;", 71 "javax/sql/rowset/serial/SerialJavaObject#getFields ()[Ljava/lang/reflect/Field;" 72 ); 73 74 public static void main(String[] args) throws Exception { 75 if (args.length > 0 && args[0].equals("--list")) { 76 listCSMs = true; 77 } 78 79 CheckCSMs checkCSMs = new CheckCSMs(); 80 Set<String> result = checkCSMs.run(getPlatformClasses()); 81 if (!KNOWN_NON_FINAL_CSMS.equals(result)) { 82 Set<String> diff = new HashSet<>(result); 83 diff.removeAll(KNOWN_NON_FINAL_CSMS); 84 throw new RuntimeException("Unexpected non-final instance method: " + 85 result.stream().sorted() 86 .collect(Collectors.joining("\n", "\n", ""))); 87 } 88 } 89 90 private final Set<String> nonFinalCSMs = new ConcurrentSkipListSet<>(); 91 private final ReferenceFinder finder; 92 public CheckCSMs() { 93 this.finder = new ReferenceFinder(getFilter(), getVisitor()); 94 pool = Executors.newFixedThreadPool(numThreads); 95 96 } 97 98 public Set<String> run(Stream<Path> classes) 99 throws IOException, InterruptedException, ExecutionException, 100 ConstantPoolException 101 { 102 classes.forEach(this::processPath); 103 waitForCompletion(); 104 pool.shutdown(); 105 return nonFinalCSMs; 106 } 107 108 109 private ReferenceFinder.Filter getFilter() { 110 final String classname = "jdk/internal/reflect/Reflection"; 111 final String method = "getCallerClass"; 112 return new ReferenceFinder.Filter() { 113 public boolean accept(ConstantPool cpool, CPRefInfo cpref) { 114 try { 115 CONSTANT_NameAndType_info nat = cpref.getNameAndTypeInfo(); 116 return cpref.getClassName().equals(classname) && nat.getName().equals(method); 117 } catch (ConstantPoolException ex) { 118 throw new RuntimeException(ex); 119 } 120 } 121 }; 122 } 123 124 private ReferenceFinder.Visitor getVisitor() { 125 return new ReferenceFinder.Visitor() { 126 public void visit(ClassFile cf, Method m, List<CPRefInfo> refs) { 127 try { 128 // ignore jdk.unsupported/sun.reflect.Reflection.getCallerClass 129 // which is a "special" delegate to the internal getCallerClass 130 if (cf.getName().equals("sun/reflect/Reflection") && 131 m.getName(cf.constant_pool).equals("getCallerClass")) 132 return; 133 134 String name = String.format("%s#%s %s", cf.getName(), 135 m.getName(cf.constant_pool), 136 m.descriptor.getValue(cf.constant_pool)); 137 if (!CheckCSMs.isStaticOrFinal(cf, m, cf.constant_pool)) { 138 System.err.println("Unsupported @CallerSensitive: " + name); 139 nonFinalCSMs.add(name); 140 } else { 141 if (listCSMs) { 142 System.out.format("@CS %s%n", name); 143 } 144 } 145 } catch (ConstantPoolException ex) { 146 throw new RuntimeException(ex); 147 } 148 } 149 }; 150 } 151 152 void processPath(Path path) { 153 try { 154 ClassFileReader reader = ClassFileReader.newInstance(path); 155 for (ClassFile cf : reader.getClassFiles()) { 156 if (cf.access_flags.is(AccessFlags.ACC_MODULE)) 157 continue; 158 159 String classFileName = cf.getName(); 160 // for each ClassFile 161 // parse constant pool to find matching method refs 162 // parse each method (caller) 163 // - visit and find method references matching the given method name 164 pool.submit(getTask(cf)); 165 } 166 } catch (IOException x) { 167 throw new UncheckedIOException(x); 168 } catch (ConstantPoolException x) { 169 throw new RuntimeException(x); 170 } 171 } 172 173 private static final String CALLER_SENSITIVE_ANNOTATION 174 = "Ljdk/internal/reflect/CallerSensitive;"; 175 176 private static boolean isCallerSensitive(Method m, ConstantPool cp) 177 throws ConstantPoolException 178 { 179 RuntimeAnnotations_attribute attr = 180 (RuntimeAnnotations_attribute)m.attributes.get(Attribute.RuntimeVisibleAnnotations); 181 if (attr != null) { 182 for (int i = 0; i < attr.annotations.length; i++) { 183 Annotation ann = attr.annotations[i]; 184 String annType = cp.getUTF8Value(ann.type_index); 185 if (CALLER_SENSITIVE_ANNOTATION.equals(annType)) { 186 return true; 187 } 188 } 189 } 190 return false; 191 } 192 193 private static boolean isStaticOrFinal(ClassFile cf, Method m, ConstantPool cp) 194 throws ConstantPoolException 195 { 196 if (!isCallerSensitive(m, cp)) 197 return false; 198 199 // either a static method or a final instance method 200 return m.access_flags.is(AccessFlags.ACC_STATIC) || 201 m.access_flags.is(AccessFlags.ACC_FINAL) || 202 cf.access_flags.is(AccessFlags.ACC_FINAL); 203 } 204 205 private final List<FutureTask<Void>> tasks = new ArrayList<FutureTask<Void>>(); 206 private FutureTask<Void> getTask(final ClassFile cf) { 207 FutureTask<Void> task = new FutureTask<Void>(new Callable<Void>() { 208 public Void call() throws Exception { 209 finder.parse(cf); 210 return null; 211 } 212 }); 213 tasks.add(task); 214 return task; 215 } 216 217 private void waitForCompletion() throws InterruptedException, ExecutionException { 218 for (FutureTask<Void> t : tasks) { 219 t.get(); 220 } 221 if (tasks.isEmpty()) { 222 throw new RuntimeException("No classes found, or specified."); 223 } 224 System.out.println("Parsed " + tasks.size() + " classfiles"); 225 } 226 227 static Stream<Path> getPlatformClasses() throws IOException { 228 Path home = Paths.get(System.getProperty("java.home")); 229 230 // Either an exploded build or an image. 231 File classes = home.resolve("modules").toFile(); 232 if (classes.isDirectory()) { 233 return Stream.of(classes.toPath()); 234 } else { 235 return jrtPaths(); 236 } 237 } 238 239 static Stream<Path> jrtPaths() { 240 FileSystem jrt = FileSystems.getFileSystem(URI.create("jrt:/")); 241 Path root = jrt.getPath("/"); 242 243 try { 244 return Files.walk(root) 245 .filter(p -> p.getNameCount() > 1) 246 .filter(p -> p.toString().endsWith(".class")); 247 } catch (IOException x) { 248 throw new UncheckedIOException(x); 249 } 250 } 251 }