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.  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 package jdk.internal.module;
27 
28 import java.io.PrintStream;
29 import java.lang.invoke.MethodHandles;
30 import java.net.URL;
31 import java.security.AccessController;
32 import java.security.CodeSource;
33 import java.security.PrivilegedAction;
34 import java.security.ProtectionDomain;
35 import java.util.HashMap;
36 import java.util.LinkedHashMap;
37 import java.util.List;
38 import java.util.Map;
39 import java.util.Objects;
40 import java.util.Set;
41 import java.util.StringJoiner;
42 import java.util.WeakHashMap;
43 import java.util.function.Supplier;
44 import java.util.stream.Collectors;
45 import static java.util.Collections.*;
46 
47 import jdk.internal.misc.JavaLangAccess;
48 import jdk.internal.misc.SharedSecrets;
49 
50 /**
51  * Supports logging of access to members of exported and concealed packages
52  * that are opened to code in unnamed modules for illegal access.
53  */
54 
55 public final class IllegalAccessLogger {
56 
57     /**
58      * Logger modes
59      */
60     public static enum Mode {
61         /**
62          * Prints a warning when an illegal access succeeds and then
63          * discards the logger so that there is no further output.
64          */
65         ONESHOT,
66         /**
67          * Print warnings when illegal access succeeds
68          */
69         WARN,
70         /**
71          * Prints warnings and a stack trace when illegal access succeeds
72          */
73         DEBUG,
74     }
75 
76     /**
77      * A builder for IllegalAccessLogger objects.
78      */
79     public static class Builder {
80         private final Mode mode;
81         private final PrintStream warningStream;
82         private final Map<Module, Set<String>> moduleToConcealedPackages;
83         private final Map<Module, Set<String>> moduleToExportedPackages;
84         private boolean complete;
85 
ensureNotComplete()86         private void ensureNotComplete() {
87             if (complete) throw new IllegalStateException();
88         }
89 
90         /**
91          * Creates a builder.
92          */
Builder(Mode mode, PrintStream warningStream)93         public Builder(Mode mode, PrintStream warningStream) {
94             this.mode = mode;
95             this.warningStream = warningStream;
96             this.moduleToConcealedPackages = new HashMap<>();
97             this.moduleToExportedPackages = new HashMap<>();
98         }
99 
100         /**
101          * Adding logging of reflective-access to any member of a type in
102          * otherwise concealed packages.
103          */
logAccessToConcealedPackages(Module m, Set<String> packages)104         public Builder logAccessToConcealedPackages(Module m, Set<String> packages) {
105             ensureNotComplete();
106             moduleToConcealedPackages.put(m, unmodifiableSet(packages));
107             return this;
108         }
109 
110         /**
111          * Adding logging of reflective-access to non-public members/types in
112          * otherwise exported (not open) packages.
113          */
logAccessToExportedPackages(Module m, Set<String> packages)114         public Builder logAccessToExportedPackages(Module m, Set<String> packages) {
115             ensureNotComplete();
116             moduleToExportedPackages.put(m, unmodifiableSet(packages));
117             return this;
118         }
119 
120         /**
121          * Builds the IllegalAccessLogger and sets it as the system-wise logger.
122          */
complete()123         public void complete() {
124             Map<Module, Set<String>> map1 = unmodifiableMap(moduleToConcealedPackages);
125             Map<Module, Set<String>> map2 = unmodifiableMap(moduleToExportedPackages);
126             logger = new IllegalAccessLogger(mode, warningStream, map1, map2);
127             complete = true;
128         }
129     }
130 
131     // need access to java.lang.Module
132     private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
133 
134     // system-wide IllegalAccessLogger
135     private static volatile IllegalAccessLogger logger;
136 
137     // logger mode
138     private final Mode mode;
139 
140     // the print stream to send the warnings
141     private final PrintStream warningStream;
142 
143     // module -> packages open for illegal access
144     private final Map<Module, Set<String>> moduleToConcealedPackages;
145     private final Map<Module, Set<String>> moduleToExportedPackages;
146 
147     // caller -> usages
148     private final Map<Class<?>, Usages> callerToUsages = new WeakHashMap<>();
149 
IllegalAccessLogger(Mode mode, PrintStream warningStream, Map<Module, Set<String>> moduleToConcealedPackages, Map<Module, Set<String>> moduleToExportedPackages)150     private IllegalAccessLogger(Mode mode,
151                                 PrintStream warningStream,
152                                 Map<Module, Set<String>> moduleToConcealedPackages,
153                                 Map<Module, Set<String>> moduleToExportedPackages)
154     {
155         this.mode = mode;
156         this.warningStream = warningStream;
157         this.moduleToConcealedPackages = moduleToConcealedPackages;
158         this.moduleToExportedPackages = moduleToExportedPackages;
159     }
160 
161     /**
162      * Returns the system-wide IllegalAccessLogger or {@code null} if there is
163      * no logger.
164      */
illegalAccessLogger()165     public static IllegalAccessLogger illegalAccessLogger() {
166         return logger;
167     }
168 
169     /**
170      * Returns true if the module exports a concealed package for illegal
171      * access.
172      */
isExportedForIllegalAccess(Module module, String pn)173     public boolean isExportedForIllegalAccess(Module module, String pn) {
174         Set<String> packages = moduleToConcealedPackages.get(module);
175         if (packages != null && packages.contains(pn))
176             return true;
177         return false;
178     }
179 
180     /**
181      * Returns true if the module opens a concealed or exported package for
182      * illegal access.
183      */
isOpenForIllegalAccess(Module module, String pn)184     public boolean isOpenForIllegalAccess(Module module, String pn) {
185         if (isExportedForIllegalAccess(module, pn))
186             return true;
187         Set<String> packages = moduleToExportedPackages.get(module);
188         if (packages != null && packages.contains(pn))
189             return true;
190         return false;
191     }
192 
193     /**
194      * Logs access to the member of a target class by a caller class if the class
195      * is in a package that is exported for illegal access.
196      *
197      * The {@code whatSupplier} supplies the message that describes the member.
198      */
logIfExportedForIllegalAccess(Class<?> caller, Class<?> target, Supplier<String> whatSupplier)199     public void logIfExportedForIllegalAccess(Class<?> caller,
200                                               Class<?> target,
201                                               Supplier<String> whatSupplier) {
202         Module targetModule = target.getModule();
203         String targetPackage = target.getPackageName();
204         if (isExportedForIllegalAccess(targetModule, targetPackage)) {
205             Module callerModule = caller.getModule();
206             if (!JLA.isReflectivelyExported(targetModule, targetPackage, callerModule)) {
207                 log(caller, whatSupplier.get());
208             }
209         }
210     }
211 
212     /**
213      * Logs access to the member of a target class by a caller class if the class
214      * is in a package that is opened for illegal access.
215      *
216      * The {@code what} parameter supplies the message that describes the member.
217      */
logIfOpenedForIllegalAccess(Class<?> caller, Class<?> target, Supplier<String> whatSupplier)218     public void logIfOpenedForIllegalAccess(Class<?> caller,
219                                             Class<?> target,
220                                             Supplier<String> whatSupplier) {
221         Module targetModule = target.getModule();
222         String targetPackage = target.getPackageName();
223         if (isOpenForIllegalAccess(targetModule, targetPackage)) {
224             Module callerModule = caller.getModule();
225             if (!JLA.isReflectivelyOpened(targetModule, targetPackage, callerModule)) {
226                 log(caller, whatSupplier.get());
227             }
228         }
229     }
230 
231     /**
232      * Logs access by caller lookup if the target class is in a package that is
233      * opened for illegal access.
234      */
logIfOpenedForIllegalAccess(MethodHandles.Lookup caller, Class<?> target)235     public void logIfOpenedForIllegalAccess(MethodHandles.Lookup caller, Class<?> target) {
236         Module targetModule = target.getModule();
237         String targetPackage = target.getPackageName();
238         if (isOpenForIllegalAccess(targetModule, targetPackage)) {
239             Class<?> callerClass = caller.lookupClass();
240             Module callerModule = callerClass.getModule();
241             if (!JLA.isReflectivelyOpened(targetModule, targetPackage, callerModule)) {
242                 URL url = codeSource(callerClass);
243                 final String source;
244                 if (url == null) {
245                     source = callerClass.getName();
246                 } else {
247                     source = callerClass.getName() + " (" + url + ")";
248                 }
249                 log(callerClass, target.getName(), () ->
250                     "WARNING: Illegal reflective access using Lookup on " + source
251                     + " to " + target);
252             }
253         }
254     }
255 
256     /**
257      * Logs access by a caller class. The {@code what} parameter describes
258      * the member being accessed.
259      */
log(Class<?> caller, String what)260     private void log(Class<?> caller, String what) {
261         log(caller, what, () -> {
262             URL url = codeSource(caller);
263             String source = caller.getName();
264             if (url != null)
265                 source += " (" + url + ")";
266             return "WARNING: Illegal reflective access by " + source + " to " + what;
267         });
268     }
269 
270     /**
271      * Log access by a caller. The {@code what} parameter describes the class or
272      * member that is being accessed. The {@code msgSupplier} supplies the log
273      * message.
274      *
275      * To reduce output, this method only logs the access if it hasn't been seen
276      * previously. "Seen previously" is implemented as a map of caller class -> Usage,
277      * where a Usage is the "what" and a hash of the stack trace. The map has weak
278      * keys so it can be expunged when the caller is GC'ed/unloaded.
279      */
log(Class<?> caller, String what, Supplier<String> msgSupplier)280     private void log(Class<?> caller, String what, Supplier<String> msgSupplier) {
281         if (mode == Mode.ONESHOT) {
282             synchronized (IllegalAccessLogger.class) {
283                 // discard the system wide logger
284                 if (logger == null)
285                     return;
286                 logger = null;
287             }
288             warningStream.println(loudWarning(caller, msgSupplier));
289             return;
290         }
291 
292         // stack trace without the top-most frames in java.base
293         List<StackWalker.StackFrame> stack = StackWalkerHolder.INSTANCE.walk(s ->
294             s.dropWhile(this::isJavaBase)
295              .limit(32)
296              .collect(Collectors.toList())
297         );
298 
299         // record usage if this is the first (or not recently recorded)
300         Usage u = new Usage(what, hash(stack));
301         boolean added;
302         synchronized (this) {
303             added = callerToUsages.computeIfAbsent(caller, k -> new Usages()).add(u);
304         }
305 
306         // print warning if this is the first (or not a recent) usage
307         if (added) {
308             String msg = msgSupplier.get();
309             if (mode == Mode.DEBUG) {
310                 StringBuilder sb = new StringBuilder(msg);
311                 stack.forEach(f ->
312                     sb.append(System.lineSeparator()).append("\tat " + f)
313                 );
314                 msg = sb.toString();
315             }
316             warningStream.println(msg);
317         }
318     }
319 
320     /**
321      * Returns the code source for the given class or null if there is no code source
322      */
codeSource(Class<?> clazz)323     private URL codeSource(Class<?> clazz) {
324         PrivilegedAction<ProtectionDomain> pa = clazz::getProtectionDomain;
325         CodeSource cs = AccessController.doPrivileged(pa).getCodeSource();
326         return (cs != null) ? cs.getLocation() : null;
327     }
328 
loudWarning(Class<?> caller, Supplier<String> msgSupplier)329     private String loudWarning(Class<?> caller,  Supplier<String> msgSupplier) {
330         StringJoiner sj = new StringJoiner(System.lineSeparator());
331         sj.add("WARNING: An illegal reflective access operation has occurred");
332         sj.add(msgSupplier.get());
333         sj.add("WARNING: Please consider reporting this to the maintainers of "
334                 + caller.getName());
335         sj.add("WARNING: Use --illegal-access=warn to enable warnings of further"
336                 + " illegal reflective access operations");
337         sj.add("WARNING: All illegal access operations will be denied in a"
338                 + " future release");
339         return sj.toString();
340     }
341 
342     private static class StackWalkerHolder {
343         static final StackWalker INSTANCE;
344         static {
345             PrivilegedAction<StackWalker> pa = () ->
346                 StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
347             INSTANCE = AccessController.doPrivileged(pa);
348         }
349     }
350 
351     /**
352      * Returns true if the stack frame is for a class in java.base.
353      */
isJavaBase(StackWalker.StackFrame frame)354     private boolean isJavaBase(StackWalker.StackFrame frame) {
355         Module caller = frame.getDeclaringClass().getModule();
356         return "java.base".equals(caller.getName());
357     }
358 
359     /**
360      * Computes a hash code for the give stack frames. The hash code is based
361      * on the class, method name, and BCI.
362      */
hash(List<StackWalker.StackFrame> stack)363     private int hash(List<StackWalker.StackFrame> stack) {
364         int hash = 0;
365         for (StackWalker.StackFrame frame : stack) {
366             hash = (31 * hash) + Objects.hash(frame.getDeclaringClass(),
367                                               frame.getMethodName(),
368                                               frame.getByteCodeIndex());
369         }
370         return hash;
371     }
372 
373     private static class Usage {
374         private final String what;
375         private final int stack;
Usage(String what, int stack)376         Usage(String what, int stack) {
377             this.what = what;
378             this.stack = stack;
379         }
380         @Override
hashCode()381         public int hashCode() {
382             return what.hashCode() ^ stack;
383         }
384         @Override
equals(Object ob)385         public boolean equals(Object ob) {
386             if (ob instanceof Usage) {
387                 Usage that = (Usage)ob;
388                 return what.equals(that.what) && stack == (that.stack);
389             } else {
390                 return false;
391             }
392         }
393     }
394 
395     @SuppressWarnings("serial")
396     private static class Usages extends LinkedHashMap<Usage, Boolean> {
Usages()397         Usages() { }
add(Usage u)398         boolean add(Usage u) {
399             return (putIfAbsent(u, Boolean.TRUE) == null);
400         }
401         @Override
removeEldestEntry(Map.Entry<Usage, Boolean> oldest)402         protected boolean removeEldestEntry(Map.Entry<Usage, Boolean> oldest) {
403             // prevent map growing too big, say where a utility class
404             // is used by generated code to do illegal access
405             return size() > 16;
406         }
407     }
408 }
409