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