|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
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. |
|
*/ |
|
|
|
public final class IllegalAccessLogger { |
|
|
|
|
|
|
|
*/ |
|
public static enum Mode { |
|
|
|
|
|
|
|
*/ |
|
ONESHOT, |
|
|
|
|
|
*/ |
|
WARN, |
|
|
|
|
|
*/ |
|
DEBUG, |
|
} |
|
|
|
|
|
|
|
*/ |
|
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(); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public Builder(Mode mode, PrintStream warningStream) { |
|
this.mode = mode; |
|
this.warningStream = warningStream; |
|
this.moduleToConcealedPackages = new HashMap<>(); |
|
this.moduleToExportedPackages = new HashMap<>(); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public Builder logAccessToConcealedPackages(Module m, Set<String> packages) { |
|
ensureNotComplete(); |
|
moduleToConcealedPackages.put(m, unmodifiableSet(packages)); |
|
return this; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public Builder logAccessToExportedPackages(Module m, Set<String> packages) { |
|
ensureNotComplete(); |
|
moduleToExportedPackages.put(m, unmodifiableSet(packages)); |
|
return this; |
|
} |
|
|
|
|
|
|
|
*/ |
|
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; |
|
} |
|
} |
|
|
|
|
|
private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); |
|
|
|
|
|
private static volatile IllegalAccessLogger logger; |
|
|
|
|
|
private final Mode mode; |
|
|
|
|
|
private final PrintStream warningStream; |
|
|
|
|
|
private final Map<Module, Set<String>> moduleToConcealedPackages; |
|
private final Map<Module, Set<String>> moduleToExportedPackages; |
|
|
|
|
|
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; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public static IllegalAccessLogger illegalAccessLogger() { |
|
return logger; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
public boolean isExportedForIllegalAccess(Module module, String pn) { |
|
Set<String> packages = moduleToConcealedPackages.get(module); |
|
if (packages != null && packages.contains(pn)) |
|
return true; |
|
return false; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
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; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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()); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
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()); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
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); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
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; |
|
}); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private void log(Class<?> caller, String what, Supplier<String> msgSupplier) { |
|
if (mode == Mode.ONESHOT) { |
|
synchronized (IllegalAccessLogger.class) { |
|
|
|
if (logger == null) |
|
return; |
|
logger = null; |
|
} |
|
warningStream.println(loudWarning(caller, msgSupplier)); |
|
return; |
|
} |
|
|
|
|
|
List<StackWalker.StackFrame> stack = StackWalkerHolder.INSTANCE.walk(s -> |
|
s.dropWhile(this::isJavaBase) |
|
.limit(32) |
|
.collect(Collectors.toList()) |
|
); |
|
|
|
|
|
Usage u = new Usage(what, hash(stack)); |
|
boolean added; |
|
synchronized (this) { |
|
added = callerToUsages.computeIfAbsent(caller, k -> new Usages()).add(u); |
|
} |
|
|
|
|
|
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); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
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); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
private boolean isJavaBase(StackWalker.StackFrame frame) { |
|
Module caller = frame.getDeclaringClass().getModule(); |
|
return "java.base".equals(caller.getName()); |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
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 |
|
|
|
return size() > 16; |
|
} |
|
} |
|
} |