/*
 * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package jdk.internal.module;

import java.io.PrintStream;
import java.lang.invoke.MethodHandles;
import java.net.URL;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.StringJoiner;
import java.util.WeakHashMap;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static java.util.Collections.*;

import jdk.internal.misc.JavaLangAccess;
import jdk.internal.misc.SharedSecrets;

Supports logging of access to members of exported and concealed packages that are opened to code in unnamed modules for illegal access.
/** * Supports logging of access to members of exported and concealed packages * that are opened to code in unnamed modules for illegal access. */
public final class IllegalAccessLogger {
Logger modes
/** * Logger modes */
public static enum Mode {
Prints a warning when an illegal access succeeds and then discards the logger so that there is no further output.
/** * Prints a warning when an illegal access succeeds and then * discards the logger so that there is no further output. */
ONESHOT,
Print warnings when illegal access succeeds
/** * Print warnings when illegal access succeeds */
WARN,
Prints warnings and a stack trace when illegal access succeeds
/** * Prints warnings and a stack trace when illegal access succeeds */
DEBUG, }
A builder for IllegalAccessLogger objects.
/** * A builder for IllegalAccessLogger objects. */
public static class Builder { private final Mode mode; private final PrintStream warningStream; private final Map<Module, Set<String>> moduleToConcealedPackages; private final Map<Module, Set<String>> moduleToExportedPackages; private boolean complete; private void ensureNotComplete() { if (complete) throw new IllegalStateException(); }
Creates a builder.
/** * Creates a builder. */
public Builder(Mode mode, PrintStream warningStream) { this.mode = mode; this.warningStream = warningStream; this.moduleToConcealedPackages = new HashMap<>(); this.moduleToExportedPackages = new HashMap<>(); }
Adding logging of reflective-access to any member of a type in otherwise concealed packages.
/** * Adding logging of reflective-access to any member of a type in * otherwise concealed packages. */
public Builder logAccessToConcealedPackages(Module m, Set<String> packages) { ensureNotComplete(); moduleToConcealedPackages.put(m, unmodifiableSet(packages)); return this; }
Adding logging of reflective-access to non-public members/types in otherwise exported (not open) packages.
/** * Adding logging of reflective-access to non-public members/types in * otherwise exported (not open) packages. */
public Builder logAccessToExportedPackages(Module m, Set<String> packages) { ensureNotComplete(); moduleToExportedPackages.put(m, unmodifiableSet(packages)); return this; }
Builds the IllegalAccessLogger and sets it as the system-wise logger.
/** * Builds the IllegalAccessLogger and sets it as the system-wise logger. */
public void complete() { Map<Module, Set<String>> map1 = unmodifiableMap(moduleToConcealedPackages); Map<Module, Set<String>> map2 = unmodifiableMap(moduleToExportedPackages); logger = new IllegalAccessLogger(mode, warningStream, map1, map2); complete = true; } } // need access to java.lang.Module private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); // system-wide IllegalAccessLogger private static volatile IllegalAccessLogger logger; // logger mode private final Mode mode; // the print stream to send the warnings private final PrintStream warningStream; // module -> packages open for illegal access private final Map<Module, Set<String>> moduleToConcealedPackages; private final Map<Module, Set<String>> moduleToExportedPackages; // caller -> usages private final Map<Class<?>, Usages> callerToUsages = new WeakHashMap<>(); private IllegalAccessLogger(Mode mode, PrintStream warningStream, Map<Module, Set<String>> moduleToConcealedPackages, Map<Module, Set<String>> moduleToExportedPackages) { this.mode = mode; this.warningStream = warningStream; this.moduleToConcealedPackages = moduleToConcealedPackages; this.moduleToExportedPackages = moduleToExportedPackages; }
Returns the system-wide IllegalAccessLogger or null if there is no logger.
/** * Returns the system-wide IllegalAccessLogger or {@code null} if there is * no logger. */
public static IllegalAccessLogger illegalAccessLogger() { return logger; }
Returns true if the module exports a concealed package for illegal access.
/** * Returns true if the module exports a concealed package for illegal * access. */
public boolean isExportedForIllegalAccess(Module module, String pn) { Set<String> packages = moduleToConcealedPackages.get(module); if (packages != null && packages.contains(pn)) return true; return false; }
Returns true if the module opens a concealed or exported package for illegal access.
/** * Returns true if the module opens a concealed or exported package for * illegal access. */
public boolean isOpenForIllegalAccess(Module module, String pn) { if (isExportedForIllegalAccess(module, pn)) return true; Set<String> packages = moduleToExportedPackages.get(module); if (packages != null && packages.contains(pn)) return true; return false; }
Logs access to the member of a target class by a caller class if the class is in a package that is exported for illegal access. The whatSupplier supplies the message that describes the member.
/** * Logs access to the member of a target class by a caller class if the class * is in a package that is exported for illegal access. * * The {@code whatSupplier} supplies the message that describes the member. */
public void logIfExportedForIllegalAccess(Class<?> caller, Class<?> target, Supplier<String> whatSupplier) { Module targetModule = target.getModule(); String targetPackage = target.getPackageName(); if (isExportedForIllegalAccess(targetModule, targetPackage)) { Module callerModule = caller.getModule(); if (!JLA.isReflectivelyExported(targetModule, targetPackage, callerModule)) { log(caller, whatSupplier.get()); } } }
Logs access to the member of a target class by a caller class if the class is in a package that is opened for illegal access. The what parameter supplies the message that describes the member.
/** * Logs access to the member of a target class by a caller class if the class * is in a package that is opened for illegal access. * * The {@code what} parameter supplies the message that describes the member. */
public void logIfOpenedForIllegalAccess(Class<?> caller, Class<?> target, Supplier<String> whatSupplier) { Module targetModule = target.getModule(); String targetPackage = target.getPackageName(); if (isOpenForIllegalAccess(targetModule, targetPackage)) { Module callerModule = caller.getModule(); if (!JLA.isReflectivelyOpened(targetModule, targetPackage, callerModule)) { log(caller, whatSupplier.get()); } } }
Logs access by caller lookup if the target class is in a package that is opened for illegal access.
/** * Logs access by caller lookup if the target class is in a package that is * opened for illegal access. */
public void logIfOpenedForIllegalAccess(MethodHandles.Lookup caller, Class<?> target) { Module targetModule = target.getModule(); String targetPackage = target.getPackageName(); if (isOpenForIllegalAccess(targetModule, targetPackage)) { Class<?> callerClass = caller.lookupClass(); Module callerModule = callerClass.getModule(); if (!JLA.isReflectivelyOpened(targetModule, targetPackage, callerModule)) { URL url = codeSource(callerClass); final String source; if (url == null) { source = callerClass.getName(); } else { source = callerClass.getName() + " (" + url + ")"; } log(callerClass, target.getName(), () -> "WARNING: Illegal reflective access using Lookup on " + source + " to " + target); } } }
Logs access by a caller class. The what parameter describes the member being accessed.
/** * Logs access by a caller class. The {@code what} parameter describes * the member being accessed. */
private void log(Class<?> caller, String what) { log(caller, what, () -> { URL url = codeSource(caller); String source = caller.getName(); if (url != null) source += " (" + url + ")"; return "WARNING: Illegal reflective access by " + source + " to " + what; }); }
Log access by a caller. The what parameter describes the class or member that is being accessed. The msgSupplier supplies the log message. To reduce output, this method only logs the access if it hasn't been seen previously. "Seen previously" is implemented as a map of caller class -> Usage, where a Usage is the "what" and a hash of the stack trace. The map has weak keys so it can be expunged when the caller is GC'ed/unloaded.
/** * Log access by a caller. The {@code what} parameter describes the class or * member that is being accessed. The {@code msgSupplier} supplies the log * message. * * To reduce output, this method only logs the access if it hasn't been seen * previously. "Seen previously" is implemented as a map of caller class -> Usage, * where a Usage is the "what" and a hash of the stack trace. The map has weak * keys so it can be expunged when the caller is GC'ed/unloaded. */
private void log(Class<?> caller, String what, Supplier<String> msgSupplier) { if (mode == Mode.ONESHOT) { synchronized (IllegalAccessLogger.class) { // discard the system wide logger if (logger == null) return; logger = null; } warningStream.println(loudWarning(caller, msgSupplier)); return; } // stack trace without the top-most frames in java.base List<StackWalker.StackFrame> stack = StackWalkerHolder.INSTANCE.walk(s -> s.dropWhile(this::isJavaBase) .limit(32) .collect(Collectors.toList()) ); // record usage if this is the first (or not recently recorded) Usage u = new Usage(what, hash(stack)); boolean added; synchronized (this) { added = callerToUsages.computeIfAbsent(caller, k -> new Usages()).add(u); } // print warning if this is the first (or not a recent) usage if (added) { String msg = msgSupplier.get(); if (mode == Mode.DEBUG) { StringBuilder sb = new StringBuilder(msg); stack.forEach(f -> sb.append(System.lineSeparator()).append("\tat " + f) ); msg = sb.toString(); } warningStream.println(msg); } }
Returns the code source for the given class or null if there is no code source
/** * Returns the code source for the given class or null if there is no code source */
private URL codeSource(Class<?> clazz) { PrivilegedAction<ProtectionDomain> pa = clazz::getProtectionDomain; CodeSource cs = AccessController.doPrivileged(pa).getCodeSource(); return (cs != null) ? cs.getLocation() : null; } private String loudWarning(Class<?> caller, Supplier<String> msgSupplier) { StringJoiner sj = new StringJoiner(System.lineSeparator()); sj.add("WARNING: An illegal reflective access operation has occurred"); sj.add(msgSupplier.get()); sj.add("WARNING: Please consider reporting this to the maintainers of " + caller.getName()); sj.add("WARNING: Use --illegal-access=warn to enable warnings of further" + " illegal reflective access operations"); sj.add("WARNING: All illegal access operations will be denied in a" + " future release"); return sj.toString(); } private static class StackWalkerHolder { static final StackWalker INSTANCE; static { PrivilegedAction<StackWalker> pa = () -> StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); INSTANCE = AccessController.doPrivileged(pa); } }
Returns true if the stack frame is for a class in java.base.
/** * Returns true if the stack frame is for a class in java.base. */
private boolean isJavaBase(StackWalker.StackFrame frame) { Module caller = frame.getDeclaringClass().getModule(); return "java.base".equals(caller.getName()); }
Computes a hash code for the give stack frames. The hash code is based on the class, method name, and BCI.
/** * Computes a hash code for the give stack frames. The hash code is based * on the class, method name, and BCI. */
private int hash(List<StackWalker.StackFrame> stack) { int hash = 0; for (StackWalker.StackFrame frame : stack) { hash = (31 * hash) + Objects.hash(frame.getDeclaringClass(), frame.getMethodName(), frame.getByteCodeIndex()); } return hash; } private static class Usage { private final String what; private final int stack; Usage(String what, int stack) { this.what = what; this.stack = stack; } @Override public int hashCode() { return what.hashCode() ^ stack; } @Override public boolean equals(Object ob) { if (ob instanceof Usage) { Usage that = (Usage)ob; return what.equals(that.what) && stack == (that.stack); } else { return false; } } } @SuppressWarnings("serial") private static class Usages extends LinkedHashMap<Usage, Boolean> { Usages() { } boolean add(Usage u) { return (putIfAbsent(u, Boolean.TRUE) == null); } @Override protected boolean removeEldestEntry(Map.Entry<Usage, Boolean> oldest) { // prevent map growing too big, say where a utility class // is used by generated code to do illegal access return size() > 16; } } }