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