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.access.JavaLangAccess; 48 import jdk.internal.access.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 86 private void ensureNotComplete() { 87 if (complete) throw new IllegalStateException(); 88 } 89 90 /** 91 * Creates a builder. 92 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 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; 376 Usage(String what, int stack) { 377 this.what = what; 378 this.stack = stack; 379 } 380 @Override 381 public int hashCode() { 382 return what.hashCode() ^ stack; 383 } 384 @Override 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> { 397 Usages() { } 398 boolean add(Usage u) { 399 return (putIfAbsent(u, Boolean.TRUE) == null); 400 } 401 @Override 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 }