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