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.lang.invoke.MethodHandles; 29 import java.lang.reflect.Module; 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.HashSet; 37 import java.util.List; 38 import java.util.Map; 39 import java.util.Objects; 40 import java.util.Set; 41 import java.util.WeakHashMap; 42 import java.util.function.Supplier; 43 import java.util.stream.Collectors; 44 45 /** 46 * Supports logging of access to members of API packages that are exported or 47 * opened via backdoor mechanisms to code in unnamed modules. 48 */ 49 50 public final class IllegalAccessLogger { 51 52 // true to print stack trace 53 private static final boolean PRINT_STACK_TRACE; 54 static { 55 String s = System.getProperty("sun.reflect.debugModuleAccessChecks"); 56 PRINT_STACK_TRACE = "access".equals(s); 57 } 58 59 private static final StackWalker STACK_WALKER 60 = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); 61 62 // the maximum number of frames to capture 63 private static final int MAX_STACK_FRAMES = 32; 64 65 // lock to avoid interference when printing stack traces 66 private static final Object OUTPUT_LOCK = new Object(); 67 68 // caller -> usages 69 private final Map<Class<?>, Set<Usage>> callerToUsages = new WeakHashMap<>(); 70 71 // module -> (package name -> CLI option) 72 private final Map<Module, Map<String, String>> exported; 73 private final Map<Module, Map<String, String>> opened; 74 75 private IllegalAccessLogger(Map<Module, Map<String, String>> exported, 76 Map<Module, Map<String, String>> opened) { 77 this.exported = deepCopy(exported); 78 this.opened = deepCopy(opened); 79 } 80 81 /** 82 * Returns that a Builder that is seeded with the packages known to this logger. 83 */ 84 public Builder toBuilder() { 85 return new Builder(exported, opened); 86 } 87 88 /** 89 * Logs access to the member of a target class by a caller class if the class 90 * is in a package that is exported via a backdoor mechanism. 91 * 92 * The {@code whatSupplier} supplies the message that describes the member. 93 */ 94 public void logIfExportedByBackdoor(Class<?> caller, 95 Class<?> target, 96 Supplier<String> whatSupplier) { 97 Map<String, String> packages = exported.get(target.getModule()); 98 if (packages != null) { 151 if (how != null) { 152 log(caller.lookupClass(), target.getName(), () -> 153 "WARNING: Illegal access using Lookup on " + caller.lookupClass() 154 + " to " + target + " (permitted by " + how + ")"); 155 } 156 } 157 } 158 159 /** 160 * Log access by a caller. The {@code what} parameter describes the class or 161 * member that is being accessed. The {@code msgSupplier} supplies the log 162 * message. 163 * 164 * To reduce output, this method only logs the access if it hasn't been seen 165 * previously. "Seen previously" is implemented as a map of caller class -> Usage, 166 * where a Usage is the "what" and a hash of the stack trace. The map has weak 167 * keys so it can be expunged when the caller is GC'ed/unloaded. 168 */ 169 private void log(Class<?> caller, String what, Supplier<String> msgSupplier) { 170 // stack trace without the top-most frames in java.base 171 List<StackWalker.StackFrame> stack = STACK_WALKER.walk(s -> 172 s.dropWhile(this::isJavaBase) 173 .limit(MAX_STACK_FRAMES) 174 .collect(Collectors.toList()) 175 ); 176 177 // check if the access has already been recorded 178 Usage u = new Usage(what, hash(stack)); 179 boolean firstUsage; 180 synchronized (this) { 181 firstUsage = callerToUsages.computeIfAbsent(caller, k -> new HashSet<>()).add(u); 182 } 183 184 // log message if first usage 185 if (firstUsage) { 186 String msg = msgSupplier.get(); 187 if (PRINT_STACK_TRACE) { 188 synchronized (OUTPUT_LOCK) { 189 System.err.println(msg); 190 stack.forEach(f -> System.err.println("\tat " + f)); 191 } 192 } else { 193 System.err.println(msg); 194 } 195 } 196 } 197 198 private static class Usage { 199 private final String what; 200 private final int stack; 201 Usage(String what, int stack) { 202 this.what = what; 203 this.stack = stack; 204 } 205 @Override 206 public int hashCode() { 207 return what.hashCode() ^ stack; 208 } 209 @Override 210 public boolean equals(Object ob) { 211 if (ob instanceof Usage) { 212 Usage that = (Usage)ob; 213 return what.equals(that.what) && stack == (that.stack); 248 public static void setIllegalAccessLogger(IllegalAccessLogger l) { 249 if (l.exported.isEmpty() && l.opened.isEmpty()) { 250 logger = null; 251 } else { 252 logger = l; 253 } 254 } 255 256 /** 257 * Returns the system-wide IllegalAccessLogger or {@code null} if there is 258 * no logger. 259 */ 260 public static IllegalAccessLogger illegalAccessLogger() { 261 return logger; 262 } 263 264 /** 265 * A builder for IllegalAccessLogger objects. 266 */ 267 public static class Builder { 268 private Map<Module, Map<String, String>> exported; 269 private Map<Module, Map<String, String>> opened; 270 271 public Builder() { } 272 273 public Builder(Map<Module, Map<String, String>> exported, 274 Map<Module, Map<String, String>> opened) { 275 this.exported = deepCopy(exported); 276 this.opened = deepCopy(opened); 277 } 278 279 public void logAccessToExportedPackage(Module m, String pn, String how) { 280 if (!m.isExported(pn)) { 281 if (exported == null) 282 exported = new HashMap<>(); 283 exported.computeIfAbsent(m, k -> new HashMap<>()).putIfAbsent(pn, how); 284 } 285 } 286 287 public void logAccessToOpenPackage(Module m, String pn, String how) { 288 // opens implies exported at run-time. 289 logAccessToExportedPackage(m, pn, how); 290 291 if (!m.isOpen(pn)) { 292 if (opened == null) 293 opened = new HashMap<>(); 294 opened.computeIfAbsent(m, k -> new HashMap<>()).putIfAbsent(pn, how); 295 } 296 } 297 298 /** 299 * Builds the logger. 300 */ 301 public IllegalAccessLogger build() { 302 return new IllegalAccessLogger(exported, opened); 303 } 304 } 305 306 307 static Map<Module, Map<String, String>> deepCopy(Map<Module, Map<String, String>> map) { 308 if (map == null || map.isEmpty()) { 309 return new HashMap<>(); 310 } else { 311 Map<Module, Map<String, String>> newMap = new HashMap<>(); 312 for (Map.Entry<Module, Map<String, String>> e : map.entrySet()) { 313 newMap.put(e.getKey(), new HashMap<>(e.getValue())); 314 } 315 return newMap; 316 } 317 } 318 } | 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.lang.reflect.Module; 31 import java.net.URL; 32 import java.security.AccessController; 33 import java.security.CodeSource; 34 import java.security.PrivilegedAction; 35 import java.security.ProtectionDomain; 36 import java.util.HashMap; 37 import java.util.HashSet; 38 import java.util.List; 39 import java.util.Map; 40 import java.util.Objects; 41 import java.util.Set; 42 import java.util.WeakHashMap; 43 import java.util.function.Supplier; 44 import java.util.stream.Collectors; 45 46 import jdk.internal.loader.BootLoader; 47 import sun.security.action.GetPropertyAction; 48 49 /** 50 * Supports logging of access to members of API packages that are exported or 51 * opened via backdoor mechanisms to code in unnamed modules. 52 */ 53 54 public final class IllegalAccessLogger { 55 56 /** 57 * Holder class to lazily create the StackWalker object and determine 58 * if the stack trace should be printed 59 */ 60 static class Holder { 61 static final StackWalker STACK_WALKER; 62 static final boolean PRINT_STACK_TRACE; 63 64 static { 65 PrivilegedAction<StackWalker> pa = () -> 66 StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); 67 STACK_WALKER = AccessController.doPrivileged(pa); 68 69 String name = "sun.reflect.debugModuleAccessChecks"; 70 String value = GetPropertyAction.privilegedGetProperty(name, null); 71 PRINT_STACK_TRACE = "access" .equals(value); 72 } 73 } 74 75 // the maximum number of frames to capture 76 private static final int MAX_STACK_FRAMES = 32; 77 78 // lock to avoid interference when printing stack traces 79 private static final Object OUTPUT_LOCK = new Object(); 80 81 // caller -> usages 82 private final Map<Class<?>, Set<Usage>> callerToUsages = new WeakHashMap<>(); 83 84 // module -> (package name -> CLI option) 85 private final Map<Module, Map<String, String>> exported; 86 private final Map<Module, Map<String, String>> opened; 87 88 // the print stream to send the warnings 89 private final PrintStream warningStream; 90 91 private IllegalAccessLogger(Map<Module, Map<String, String>> exported, 92 Map<Module, Map<String, String>> opened, 93 PrintStream warningStream) { 94 this.exported = deepCopy(exported); 95 this.opened = deepCopy(opened); 96 this.warningStream = warningStream; 97 } 98 99 /** 100 * Returns that a Builder that is seeded with the packages known to this logger. 101 */ 102 public Builder toBuilder() { 103 return new Builder(exported, opened); 104 } 105 106 /** 107 * Logs access to the member of a target class by a caller class if the class 108 * is in a package that is exported via a backdoor mechanism. 109 * 110 * The {@code whatSupplier} supplies the message that describes the member. 111 */ 112 public void logIfExportedByBackdoor(Class<?> caller, 113 Class<?> target, 114 Supplier<String> whatSupplier) { 115 Map<String, String> packages = exported.get(target.getModule()); 116 if (packages != null) { 169 if (how != null) { 170 log(caller.lookupClass(), target.getName(), () -> 171 "WARNING: Illegal access using Lookup on " + caller.lookupClass() 172 + " to " + target + " (permitted by " + how + ")"); 173 } 174 } 175 } 176 177 /** 178 * Log access by a caller. The {@code what} parameter describes the class or 179 * member that is being accessed. The {@code msgSupplier} supplies the log 180 * message. 181 * 182 * To reduce output, this method only logs the access if it hasn't been seen 183 * previously. "Seen previously" is implemented as a map of caller class -> Usage, 184 * where a Usage is the "what" and a hash of the stack trace. The map has weak 185 * keys so it can be expunged when the caller is GC'ed/unloaded. 186 */ 187 private void log(Class<?> caller, String what, Supplier<String> msgSupplier) { 188 // stack trace without the top-most frames in java.base 189 List<StackWalker.StackFrame> stack = Holder.STACK_WALKER.walk(s -> 190 s.dropWhile(this::isJavaBase) 191 .limit(MAX_STACK_FRAMES) 192 .collect(Collectors.toList()) 193 ); 194 195 // check if the access has already been recorded 196 Usage u = new Usage(what, hash(stack)); 197 boolean firstUsage; 198 synchronized (this) { 199 firstUsage = callerToUsages.computeIfAbsent(caller, k -> new HashSet<>()).add(u); 200 } 201 202 // log message if first usage 203 if (firstUsage) { 204 String msg = msgSupplier.get(); 205 if (Holder.PRINT_STACK_TRACE) { 206 synchronized (OUTPUT_LOCK) { 207 warningStream.println(msg); 208 stack.forEach(f -> warningStream.println("\tat " + f)); 209 } 210 } else { 211 warningStream.println(msg); 212 } 213 } 214 } 215 216 private static class Usage { 217 private final String what; 218 private final int stack; 219 Usage(String what, int stack) { 220 this.what = what; 221 this.stack = stack; 222 } 223 @Override 224 public int hashCode() { 225 return what.hashCode() ^ stack; 226 } 227 @Override 228 public boolean equals(Object ob) { 229 if (ob instanceof Usage) { 230 Usage that = (Usage)ob; 231 return what.equals(that.what) && stack == (that.stack); 266 public static void setIllegalAccessLogger(IllegalAccessLogger l) { 267 if (l.exported.isEmpty() && l.opened.isEmpty()) { 268 logger = null; 269 } else { 270 logger = l; 271 } 272 } 273 274 /** 275 * Returns the system-wide IllegalAccessLogger or {@code null} if there is 276 * no logger. 277 */ 278 public static IllegalAccessLogger illegalAccessLogger() { 279 return logger; 280 } 281 282 /** 283 * A builder for IllegalAccessLogger objects. 284 */ 285 public static class Builder { 286 private final Module UNNAMED = BootLoader.getUnnamedModule(); 287 private Map<Module, Map<String, String>> exported; 288 private Map<Module, Map<String, String>> opened; 289 private PrintStream warningStream = System.err; 290 291 public Builder() { } 292 293 public Builder(Map<Module, Map<String, String>> exported, 294 Map<Module, Map<String, String>> opened) { 295 this.exported = deepCopy(exported); 296 this.opened = deepCopy(opened); 297 } 298 299 public Builder logAccessToExportedPackage(Module m, String pn, String how) { 300 if (!m.isExported(pn, UNNAMED)) { 301 if (exported == null) 302 exported = new HashMap<>(); 303 exported.computeIfAbsent(m, k -> new HashMap<>()).putIfAbsent(pn, how); 304 } 305 return this; 306 } 307 308 public Builder logAccessToOpenPackage(Module m, String pn, String how) { 309 // opens implies exported at run-time. 310 logAccessToExportedPackage(m, pn, how); 311 312 if (!m.isOpen(pn, UNNAMED)) { 313 if (opened == null) 314 opened = new HashMap<>(); 315 opened.computeIfAbsent(m, k -> new HashMap<>()).putIfAbsent(pn, how); 316 } 317 return this; 318 } 319 320 public Builder warningStream(PrintStream warningStream) { 321 this.warningStream = Objects.requireNonNull(warningStream); 322 return this; 323 } 324 325 /** 326 * Builds the logger. 327 */ 328 public IllegalAccessLogger build() { 329 return new IllegalAccessLogger(exported, opened, warningStream); 330 } 331 } 332 333 334 static Map<Module, Map<String, String>> deepCopy(Map<Module, Map<String, String>> map) { 335 if (map == null || map.isEmpty()) { 336 return new HashMap<>(); 337 } else { 338 Map<Module, Map<String, String>> newMap = new HashMap<>(); 339 for (Map.Entry<Module, Map<String, String>> e : map.entrySet()) { 340 newMap.put(e.getKey(), new HashMap<>(e.getValue())); 341 } 342 return newMap; 343 } 344 } 345 } |