8314263: Signed jars triggering Logger finder recursion and StackOverflowError
Co-authored-by: Daniel Fuchs <dfuchs@openjdk.org> Reviewed-by: dfuchs
This commit is contained in:
parent
6701eba736
commit
7daae1fb42
@ -68,6 +68,7 @@ import java.util.function.Supplier;
|
|||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import jdk.internal.logger.LoggerFinderLoader.TemporaryLoggerFinder;
|
||||||
import jdk.internal.misc.CarrierThreadLocal;
|
import jdk.internal.misc.CarrierThreadLocal;
|
||||||
import jdk.internal.misc.Unsafe;
|
import jdk.internal.misc.Unsafe;
|
||||||
import jdk.internal.util.StaticProperty;
|
import jdk.internal.util.StaticProperty;
|
||||||
@ -1766,13 +1767,16 @@ public final class System {
|
|||||||
// We do not need to synchronize: LoggerFinderLoader will
|
// We do not need to synchronize: LoggerFinderLoader will
|
||||||
// always return the same instance, so if we don't have it,
|
// always return the same instance, so if we don't have it,
|
||||||
// just fetch it again.
|
// just fetch it again.
|
||||||
if (service == null) {
|
LoggerFinder finder = service;
|
||||||
|
if (finder == null) {
|
||||||
PrivilegedAction<LoggerFinder> pa =
|
PrivilegedAction<LoggerFinder> pa =
|
||||||
() -> LoggerFinderLoader.getLoggerFinder();
|
() -> LoggerFinderLoader.getLoggerFinder();
|
||||||
service = AccessController.doPrivileged(pa, null,
|
finder = AccessController.doPrivileged(pa, null,
|
||||||
LOGGERFINDER_PERMISSION);
|
LOGGERFINDER_PERMISSION);
|
||||||
|
if (finder instanceof TemporaryLoggerFinder) return finder;
|
||||||
|
service = finder;
|
||||||
}
|
}
|
||||||
return service;
|
return finder;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -38,7 +38,6 @@ import java.util.function.Function;
|
|||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.lang.System.LoggerFinder;
|
import java.lang.System.LoggerFinder;
|
||||||
import java.lang.System.Logger;
|
import java.lang.System.Logger;
|
||||||
import java.lang.System.Logger.Level;
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
@ -227,9 +226,19 @@ public final class BootstrapLogger implements Logger, PlatformLogger.Bridge,
|
|||||||
|
|
||||||
// The accessor in which this logger is temporarily set.
|
// The accessor in which this logger is temporarily set.
|
||||||
final LazyLoggerAccessor holder;
|
final LazyLoggerAccessor holder;
|
||||||
|
// tests whether the logger is invoked by the loading thread before
|
||||||
|
// the LoggerFinder is loaded; can be null;
|
||||||
|
final BooleanSupplier isLoadingThread;
|
||||||
|
|
||||||
BootstrapLogger(LazyLoggerAccessor holder) {
|
// returns true if the logger is invoked by the loading thread before the
|
||||||
|
// LoggerFinder service is loaded
|
||||||
|
boolean isLoadingThread() {
|
||||||
|
return isLoadingThread != null && isLoadingThread.getAsBoolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
BootstrapLogger(LazyLoggerAccessor holder, BooleanSupplier isLoadingThread) {
|
||||||
this.holder = holder;
|
this.holder = holder;
|
||||||
|
this.isLoadingThread = isLoadingThread;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Temporary data object storing log events
|
// Temporary data object storing log events
|
||||||
@ -505,14 +514,15 @@ public final class BootstrapLogger implements Logger, PlatformLogger.Bridge,
|
|||||||
static void log(LogEvent log, PlatformLogger.Bridge logger) {
|
static void log(LogEvent log, PlatformLogger.Bridge logger) {
|
||||||
final SecurityManager sm = System.getSecurityManager();
|
final SecurityManager sm = System.getSecurityManager();
|
||||||
if (sm == null || log.acc == null) {
|
if (sm == null || log.acc == null) {
|
||||||
log.log(logger);
|
BootstrapExecutors.submit(() -> log.log(logger));
|
||||||
} else {
|
} else {
|
||||||
// not sure we can actually use lambda here. We may need to create
|
// not sure we can actually use lambda here. We may need to create
|
||||||
// an anonymous class. Although if we reach here, then it means
|
// an anonymous class. Although if we reach here, then it means
|
||||||
// the VM is booted.
|
// the VM is booted.
|
||||||
|
BootstrapExecutors.submit(() ->
|
||||||
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
|
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
|
||||||
log.log(logger); return null;
|
log.log(logger); return null;
|
||||||
}, log.acc);
|
}, log.acc));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -559,8 +569,9 @@ public final class BootstrapLogger implements Logger, PlatformLogger.Bridge,
|
|||||||
* @return true if the VM is still bootstrapping.
|
* @return true if the VM is still bootstrapping.
|
||||||
*/
|
*/
|
||||||
boolean checkBootstrapping() {
|
boolean checkBootstrapping() {
|
||||||
if (isBooted()) {
|
if (isBooted() && !isLoadingThread()) {
|
||||||
BootstrapExecutors.flush();
|
BootstrapExecutors.flush();
|
||||||
|
holder.getConcreteLogger(this);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -935,10 +946,16 @@ public final class BootstrapLogger implements Logger, PlatformLogger.Bridge,
|
|||||||
// - the logging backend is a custom backend
|
// - the logging backend is a custom backend
|
||||||
// - the logging backend is JUL, there is no custom config,
|
// - the logging backend is JUL, there is no custom config,
|
||||||
// and the LogManager has not been initialized yet.
|
// and the LogManager has not been initialized yet.
|
||||||
public static synchronized boolean useLazyLoggers() {
|
public static boolean useLazyLoggers() {
|
||||||
return !BootstrapLogger.isBooted()
|
// Note: avoid triggering the initialization of the DetectBackend class
|
||||||
|| DetectBackend.detectedBackend == LoggingBackend.CUSTOM
|
// while holding the BootstrapLogger class monitor
|
||||||
|| useSurrogateLoggers();
|
if (!BootstrapLogger.isBooted() ||
|
||||||
|
DetectBackend.detectedBackend == LoggingBackend.CUSTOM) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
synchronized (BootstrapLogger.class) {
|
||||||
|
return useSurrogateLoggers();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called by LazyLoggerAccessor. This method will determine whether
|
// Called by LazyLoggerAccessor. This method will determine whether
|
||||||
@ -946,9 +963,9 @@ public final class BootstrapLogger implements Logger, PlatformLogger.Bridge,
|
|||||||
// a SurrogateLogger (if JUL is the default backend and there
|
// a SurrogateLogger (if JUL is the default backend and there
|
||||||
// is no custom JUL configuration and LogManager is not yet initialized),
|
// is no custom JUL configuration and LogManager is not yet initialized),
|
||||||
// or a logger returned by the loaded LoggerFinder (all other cases).
|
// or a logger returned by the loaded LoggerFinder (all other cases).
|
||||||
static Logger getLogger(LazyLoggerAccessor accessor) {
|
static Logger getLogger(LazyLoggerAccessor accessor, BooleanSupplier isLoading) {
|
||||||
if (!BootstrapLogger.isBooted()) {
|
if (!BootstrapLogger.isBooted() || isLoading != null && isLoading.getAsBoolean()) {
|
||||||
return new BootstrapLogger(accessor);
|
return new BootstrapLogger(accessor, isLoading);
|
||||||
} else {
|
} else {
|
||||||
if (useSurrogateLoggers()) {
|
if (useSurrogateLoggers()) {
|
||||||
// JUL is the default backend, there is no custom configuration,
|
// JUL is the default backend, there is no custom configuration,
|
||||||
@ -964,6 +981,12 @@ public final class BootstrapLogger implements Logger, PlatformLogger.Bridge,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// trigger class initialization outside of holding lock
|
||||||
|
static void ensureBackendDetected() {
|
||||||
|
assert VM.isBooted() : "VM is not booted";
|
||||||
|
// triggers detection of the backend
|
||||||
|
var backend = DetectBackend.detectedBackend;
|
||||||
|
}
|
||||||
|
|
||||||
// If the backend is JUL, and there is no custom configuration, and
|
// If the backend is JUL, and there is no custom configuration, and
|
||||||
// nobody has attempted to call LogManager.getLogManager() yet, then
|
// nobody has attempted to call LogManager.getLogManager() yet, then
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -32,6 +32,9 @@ import java.lang.System.LoggerFinder;
|
|||||||
import java.lang.System.Logger;
|
import java.lang.System.Logger;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.function.BooleanSupplier;
|
||||||
|
|
||||||
|
import jdk.internal.logger.LoggerFinderLoader.TemporaryLoggerFinder;
|
||||||
import jdk.internal.misc.VM;
|
import jdk.internal.misc.VM;
|
||||||
import sun.util.logging.PlatformLogger;
|
import sun.util.logging.PlatformLogger;
|
||||||
|
|
||||||
@ -110,6 +113,9 @@ public final class LazyLoggers {
|
|||||||
// We need to pass the actual caller module when creating the logger.
|
// We need to pass the actual caller module when creating the logger.
|
||||||
private final WeakReference<Module> moduleRef;
|
private final WeakReference<Module> moduleRef;
|
||||||
|
|
||||||
|
// whether this is the loading thread, can be null
|
||||||
|
private final BooleanSupplier isLoadingThread;
|
||||||
|
|
||||||
// The name of the logger that will be created lazyly
|
// The name of the logger that will be created lazyly
|
||||||
final String name;
|
final String name;
|
||||||
// The plain logger SPI object - null until it is accessed for the
|
// The plain logger SPI object - null until it is accessed for the
|
||||||
@ -122,16 +128,24 @@ public final class LazyLoggers {
|
|||||||
private LazyLoggerAccessor(String name,
|
private LazyLoggerAccessor(String name,
|
||||||
LazyLoggerFactories<? extends Logger> factories,
|
LazyLoggerFactories<? extends Logger> factories,
|
||||||
Module module) {
|
Module module) {
|
||||||
this(Objects.requireNonNull(name), Objects.requireNonNull(factories),
|
this(name, factories, module, null);
|
||||||
Objects.requireNonNull(module), null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private LazyLoggerAccessor(String name,
|
private LazyLoggerAccessor(String name,
|
||||||
LazyLoggerFactories<? extends Logger> factories,
|
LazyLoggerFactories<? extends Logger> factories,
|
||||||
Module module, Void unused) {
|
Module module, BooleanSupplier isLoading) {
|
||||||
|
|
||||||
|
this(Objects.requireNonNull(name), Objects.requireNonNull(factories),
|
||||||
|
Objects.requireNonNull(module), isLoading, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private LazyLoggerAccessor(String name,
|
||||||
|
LazyLoggerFactories<? extends Logger> factories,
|
||||||
|
Module module, BooleanSupplier isLoading, Void unused) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.factories = factories;
|
this.factories = factories;
|
||||||
this.moduleRef = new WeakReference<>(module);
|
this.moduleRef = new WeakReference<>(module);
|
||||||
|
this.isLoadingThread = isLoading;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -162,7 +176,7 @@ public final class LazyLoggers {
|
|||||||
// BootstrapLogger has the logic to decide whether to invoke the
|
// BootstrapLogger has the logic to decide whether to invoke the
|
||||||
// SPI or use a temporary (BootstrapLogger or SimpleConsoleLogger)
|
// SPI or use a temporary (BootstrapLogger or SimpleConsoleLogger)
|
||||||
// logger.
|
// logger.
|
||||||
wrapped = BootstrapLogger.getLogger(this);
|
wrapped = BootstrapLogger.getLogger(this, isLoadingThread);
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
// if w has already been in between, simply drop 'wrapped'.
|
// if w has already been in between, simply drop 'wrapped'.
|
||||||
setWrappedIfNotSet(wrapped);
|
setWrappedIfNotSet(wrapped);
|
||||||
@ -194,7 +208,7 @@ public final class LazyLoggers {
|
|||||||
// BootstrapLogger has the logic to decide whether to invoke the
|
// BootstrapLogger has the logic to decide whether to invoke the
|
||||||
// SPI or use a temporary (BootstrapLogger or SimpleConsoleLogger)
|
// SPI or use a temporary (BootstrapLogger or SimpleConsoleLogger)
|
||||||
// logger.
|
// logger.
|
||||||
final Logger wrapped = BootstrapLogger.getLogger(this);
|
final Logger wrapped = BootstrapLogger.getLogger(this, isLoadingThread);
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
// if w has already been set, simply drop 'wrapped'.
|
// if w has already been set, simply drop 'wrapped'.
|
||||||
setWrappedIfNotSet(wrapped);
|
setWrappedIfNotSet(wrapped);
|
||||||
@ -282,10 +296,10 @@ public final class LazyLoggers {
|
|||||||
* Creates a new lazy logger accessor for the named logger. The given
|
* Creates a new lazy logger accessor for the named logger. The given
|
||||||
* factories will be use when it becomes necessary to actually create
|
* factories will be use when it becomes necessary to actually create
|
||||||
* the logger.
|
* the logger.
|
||||||
* @param <T> An interface that extends {@link Logger}.
|
|
||||||
* @param name The logger name.
|
* @param name The logger name.
|
||||||
* @param factories The factories that should be used to create the
|
* @param factories The factories that should be used to create the
|
||||||
* wrapped logger.
|
* wrapped logger.
|
||||||
|
* @param module The module for which the logger is being created
|
||||||
* @return A new LazyLoggerAccessor.
|
* @return A new LazyLoggerAccessor.
|
||||||
*/
|
*/
|
||||||
public static LazyLoggerAccessor makeAccessor(String name,
|
public static LazyLoggerAccessor makeAccessor(String name,
|
||||||
@ -340,6 +354,7 @@ public final class LazyLoggers {
|
|||||||
prov = sm == null ? LoggerFinder.getLoggerFinder() :
|
prov = sm == null ? LoggerFinder.getLoggerFinder() :
|
||||||
AccessController.doPrivileged(
|
AccessController.doPrivileged(
|
||||||
(PrivilegedAction<LoggerFinder>)LoggerFinder::getLoggerFinder);
|
(PrivilegedAction<LoggerFinder>)LoggerFinder::getLoggerFinder);
|
||||||
|
if (prov instanceof TemporaryLoggerFinder) return prov;
|
||||||
provider = prov;
|
provider = prov;
|
||||||
}
|
}
|
||||||
return prov;
|
return prov;
|
||||||
@ -359,7 +374,6 @@ public final class LazyLoggers {
|
|||||||
new LazyLoggerFactories<>(loggerSupplier);
|
new LazyLoggerFactories<>(loggerSupplier);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// A concrete implementation of Logger that delegates to a System.Logger,
|
// A concrete implementation of Logger that delegates to a System.Logger,
|
||||||
// but only creates the System.Logger instance lazily when it's used for
|
// but only creates the System.Logger instance lazily when it's used for
|
||||||
// the first time.
|
// the first time.
|
||||||
@ -377,6 +391,11 @@ public final class LazyLoggers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Logger makeLazyLogger(String name, Module module, BooleanSupplier isLoading) {
|
||||||
|
final LazyLoggerAccessor holder = new LazyLoggerAccessor(name, factories, module, isLoading);
|
||||||
|
return new JdkLazyLogger(holder, null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a logger from the LoggerFinder. Creates the actual concrete
|
* Gets a logger from the LoggerFinder. Creates the actual concrete
|
||||||
* logger.
|
* logger.
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -25,6 +25,8 @@
|
|||||||
package jdk.internal.logger;
|
package jdk.internal.logger;
|
||||||
|
|
||||||
import java.io.FilePermission;
|
import java.io.FilePermission;
|
||||||
|
import java.lang.System.Logger;
|
||||||
|
import java.lang.System.LoggerFinder;
|
||||||
import java.security.AccessController;
|
import java.security.AccessController;
|
||||||
import java.security.Permission;
|
import java.security.Permission;
|
||||||
import java.security.PrivilegedAction;
|
import java.security.PrivilegedAction;
|
||||||
@ -32,6 +34,9 @@ import java.util.Iterator;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.ServiceConfigurationError;
|
import java.util.ServiceConfigurationError;
|
||||||
import java.util.ServiceLoader;
|
import java.util.ServiceLoader;
|
||||||
|
import java.util.function.BooleanSupplier;
|
||||||
|
|
||||||
|
import jdk.internal.vm.annotation.Stable;
|
||||||
import sun.security.util.SecurityConstants;
|
import sun.security.util.SecurityConstants;
|
||||||
import sun.security.action.GetBooleanAction;
|
import sun.security.action.GetBooleanAction;
|
||||||
import sun.security.action.GetPropertyAction;
|
import sun.security.action.GetPropertyAction;
|
||||||
@ -65,13 +70,28 @@ public final class LoggerFinderLoader {
|
|||||||
throw new InternalError("LoggerFinderLoader cannot be instantiated");
|
throw new InternalError("LoggerFinderLoader cannot be instantiated");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// record the loadingThread while loading the backend
|
||||||
|
static volatile Thread loadingThread;
|
||||||
// Return the loaded LoggerFinder, or load it if not already loaded.
|
// Return the loaded LoggerFinder, or load it if not already loaded.
|
||||||
private static System.LoggerFinder service() {
|
private static System.LoggerFinder service() {
|
||||||
if (service != null) return service;
|
if (service != null) return service;
|
||||||
|
// ensure backend is detected before attempting to load the finder
|
||||||
|
BootstrapLogger.ensureBackendDetected();
|
||||||
synchronized(lock) {
|
synchronized(lock) {
|
||||||
if (service != null) return service;
|
if (service != null) return service;
|
||||||
|
Thread currentThread = Thread.currentThread();
|
||||||
|
if (loadingThread == currentThread) {
|
||||||
|
// recursive attempt to load the backend while loading the backend
|
||||||
|
// use a temporary logger finder that returns special BootstrapLogger
|
||||||
|
// which will wait until loading is finished
|
||||||
|
return TemporaryLoggerFinder.INSTANCE;
|
||||||
|
}
|
||||||
|
loadingThread = currentThread;
|
||||||
|
try {
|
||||||
service = loadLoggerFinder();
|
service = loadLoggerFinder();
|
||||||
|
} finally {
|
||||||
|
loadingThread = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Since the LoggerFinder is already loaded - we can stop using
|
// Since the LoggerFinder is already loaded - we can stop using
|
||||||
// temporary loggers.
|
// temporary loggers.
|
||||||
@ -79,6 +99,12 @@ public final class LoggerFinderLoader {
|
|||||||
return service;
|
return service;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// returns true if called by the thread that loads the LoggerFinder, while
|
||||||
|
// loading the LoggerFinder.
|
||||||
|
static boolean isLoadingThread() {
|
||||||
|
return loadingThread != null && loadingThread == Thread.currentThread();
|
||||||
|
}
|
||||||
|
|
||||||
// Get configuration error policy
|
// Get configuration error policy
|
||||||
private static ErrorPolicy configurationErrorPolicy() {
|
private static ErrorPolicy configurationErrorPolicy() {
|
||||||
String errorPolicy =
|
String errorPolicy =
|
||||||
@ -117,6 +143,34 @@ public final class LoggerFinderLoader {
|
|||||||
return iterator;
|
return iterator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static final class TemporaryLoggerFinder extends LoggerFinder {
|
||||||
|
private TemporaryLoggerFinder() {}
|
||||||
|
@Stable
|
||||||
|
private LoggerFinder loadedService;
|
||||||
|
|
||||||
|
private static final BooleanSupplier isLoadingThread = new BooleanSupplier() {
|
||||||
|
@Override
|
||||||
|
public boolean getAsBoolean() {
|
||||||
|
return LoggerFinderLoader.isLoadingThread();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private static final TemporaryLoggerFinder INSTANCE = new TemporaryLoggerFinder();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Logger getLogger(String name, Module module) {
|
||||||
|
if (loadedService == null) {
|
||||||
|
loadedService = service;
|
||||||
|
if (loadedService == null) {
|
||||||
|
return LazyLoggers.makeLazyLogger(name, module, isLoadingThread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert loadedService != null;
|
||||||
|
assert !LoggerFinderLoader.isLoadingThread();
|
||||||
|
assert loadedService != this;
|
||||||
|
return LazyLoggers.getLogger(name, module);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Loads the LoggerFinder using ServiceLoader. If no LoggerFinder
|
// Loads the LoggerFinder using ServiceLoader. If no LoggerFinder
|
||||||
// is found returns the default (possibly JUL based) implementation
|
// is found returns the default (possibly JUL based) implementation
|
||||||
private static System.LoggerFinder loadLoggerFinder() {
|
private static System.LoggerFinder loadLoggerFinder() {
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
loggerfinder.SimpleLoggerFinder
|
@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023, 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.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @bug 8314263
|
||||||
|
* @summary Creating a logger while loading the Logger finder
|
||||||
|
* triggers recursion and StackOverflowError
|
||||||
|
* @modules java.base/sun.util.logging
|
||||||
|
* @library /test/lib
|
||||||
|
* @compile RecursiveLoadingTest.java SimpleLoggerFinder.java
|
||||||
|
* @run main/othervm PlatformRecursiveLoadingTest
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.LogRecord;
|
||||||
|
|
||||||
|
import sun.util.logging.PlatformLogger;
|
||||||
|
|
||||||
|
public class PlatformRecursiveLoadingTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test triggers recursion by calling `System.getLogger` in the class init and constructor
|
||||||
|
* of a custom LoggerFinder. Without the fix, this is expected to throw
|
||||||
|
* java.lang.NoClassDefFoundError: Could not initialize class jdk.internal.logger.LoggerFinderLoader$ErrorPolicy
|
||||||
|
* caused by: java.lang.StackOverflowError
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) throws Throwable {
|
||||||
|
PlatformLogger.getLogger("main").info("in main");
|
||||||
|
List<Object> logs = loggerfinder.SimpleLoggerFinder.LOGS;
|
||||||
|
logs.stream().map(SimpleLogRecord::of).forEach(System.out::println);
|
||||||
|
logs.stream().map(SimpleLogRecord::of).forEach(SimpleLogRecord::check);
|
||||||
|
assertEquals(String.valueOf(logs.size()), String.valueOf(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<Object> asList(Object[] params) {
|
||||||
|
return params == null ? null : Arrays.asList(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
record SimpleLogRecord(String message, Instant instant, String loggerName,
|
||||||
|
java.util.logging.Level level, List<Object> params,
|
||||||
|
String resourceBundleName, long seqNumber,
|
||||||
|
String sourceClassName, String methodName, Throwable thrown) {
|
||||||
|
SimpleLogRecord(LogRecord record) {
|
||||||
|
this(record.getMessage(), record.getInstant(), record.getLoggerName(), record.getLevel(),
|
||||||
|
asList(record.getParameters()), record.getResourceBundleName(), record.getSequenceNumber(),
|
||||||
|
record.getSourceClassName(), record.getSourceMethodName(), record.getThrown());
|
||||||
|
}
|
||||||
|
static SimpleLogRecord of(Object o) {
|
||||||
|
return (o instanceof LogRecord record) ? new SimpleLogRecord(record) : null;
|
||||||
|
}
|
||||||
|
static SimpleLogRecord check(SimpleLogRecord record) {
|
||||||
|
if (record.loggerName.equals("dummy")) {
|
||||||
|
assertEquals(record.sourceClassName, "jdk.internal.logger.BootstrapLogger$LogEvent");
|
||||||
|
assertEquals(record.methodName(), "log");
|
||||||
|
}
|
||||||
|
if (record.loggerName.equals("main")) {
|
||||||
|
assertEquals(record.sourceClassName, PlatformRecursiveLoadingTest.class.getName());
|
||||||
|
assertEquals(record.methodName, "main");
|
||||||
|
}
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertEquals(String received, String expected) {
|
||||||
|
if (!expected.equals(received)) {
|
||||||
|
throw new RuntimeException("Received: " + received);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023, 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.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @bug 8314263
|
||||||
|
* @summary Creating a logger while loading the Logger finder
|
||||||
|
* triggers recursion and StackOverflowError
|
||||||
|
* @compile RecursiveLoadingTest.java SimpleLoggerFinder.java
|
||||||
|
* @run main/othervm RecursiveLoadingTest
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.LogRecord;
|
||||||
|
|
||||||
|
public class RecursiveLoadingTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test triggers recursion by calling `System.getLogger` in the class init and constructor
|
||||||
|
* of a custom LoggerFinder. Without the fix, this is expected to throw
|
||||||
|
* java.lang.NoClassDefFoundError: Could not initialize class jdk.internal.logger.LoggerFinderLoader$ErrorPolicy
|
||||||
|
* caused by: java.lang.StackOverflowError
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) throws Throwable {
|
||||||
|
System.getLogger("main").log(System.Logger.Level.INFO, "in main");
|
||||||
|
List<Object> logs = loggerfinder.SimpleLoggerFinder.LOGS;
|
||||||
|
logs.stream().map(SimpleLogRecord::of).forEach(System.out::println);
|
||||||
|
logs.stream().map(SimpleLogRecord::of).forEach(SimpleLogRecord::check);
|
||||||
|
assertEquals(String.valueOf(logs.size()), String.valueOf(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<Object> asList(Object[] params) {
|
||||||
|
return params == null ? null : Arrays.asList(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
record SimpleLogRecord(String message, Instant instant, String loggerName,
|
||||||
|
java.util.logging.Level level, List<Object> params,
|
||||||
|
String resourceBundleName, long seqNumber,
|
||||||
|
String sourceClassName, String methodName, Throwable thrown) {
|
||||||
|
SimpleLogRecord(LogRecord record) {
|
||||||
|
this(record.getMessage(), record.getInstant(), record.getLoggerName(), record.getLevel(),
|
||||||
|
asList(record.getParameters()), record.getResourceBundleName(), record.getSequenceNumber(),
|
||||||
|
record.getSourceClassName(), record.getSourceMethodName(), record.getThrown());
|
||||||
|
}
|
||||||
|
static SimpleLogRecord of(Object o) {
|
||||||
|
return (o instanceof LogRecord record) ? new SimpleLogRecord(record) : null;
|
||||||
|
}
|
||||||
|
static SimpleLogRecord check(SimpleLogRecord record) {
|
||||||
|
if (record.loggerName.equals("dummy")) {
|
||||||
|
assertEquals(record.sourceClassName, "jdk.internal.logger.BootstrapLogger$LogEvent");
|
||||||
|
assertEquals(record.methodName(), "log");
|
||||||
|
}
|
||||||
|
if (record.loggerName.equals("main")) {
|
||||||
|
assertEquals(record.sourceClassName, RecursiveLoadingTest.class.getName());
|
||||||
|
assertEquals(record.methodName, "main");
|
||||||
|
}
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertEquals(String received, String expected) {
|
||||||
|
if (!expected.equals(received)) {
|
||||||
|
throw new RuntimeException("Received: " + received);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,132 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023, 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.
|
||||||
|
*
|
||||||
|
* 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 loggerfinder;
|
||||||
|
|
||||||
|
import java.lang.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.logging.Handler;
|
||||||
|
import java.util.logging.LogRecord;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
public class SimpleLoggerFinder extends System.LoggerFinder {
|
||||||
|
|
||||||
|
public static final CopyOnWriteArrayList<Object> LOGS = new CopyOnWriteArrayList<>();
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
long sleep = new Random().nextLong(1000L) + 1L;
|
||||||
|
// simulate a slow load service
|
||||||
|
Thread.sleep(sleep);
|
||||||
|
System.getLogger("dummy")
|
||||||
|
.log(System.Logger.Level.INFO,
|
||||||
|
"Logger finder service load sleep value: " + sleep);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Map<String, SimpleLogger> loggers = new ConcurrentHashMap<>();
|
||||||
|
public SimpleLoggerFinder() {
|
||||||
|
System.getLogger("dummy")
|
||||||
|
.log(System.Logger.Level.INFO,
|
||||||
|
"Logger finder service created");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public System.Logger getLogger(String name, Module module) {
|
||||||
|
return loggers.computeIfAbsent(name, SimpleLogger::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class SimpleLogger implements System.Logger {
|
||||||
|
private final java.util.logging.Logger logger;
|
||||||
|
|
||||||
|
private static final class SimpleHandler extends Handler {
|
||||||
|
@Override
|
||||||
|
public void publish(LogRecord record) {
|
||||||
|
LOGS.add(record);
|
||||||
|
}
|
||||||
|
@Override public void flush() { }
|
||||||
|
@Override public void close() { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleLogger(String name) {
|
||||||
|
logger = Logger.getLogger(name);
|
||||||
|
logger.addHandler(new SimpleHandler());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return logger.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
java.util.logging.Level level(Level level) {
|
||||||
|
return switch (level) {
|
||||||
|
case ALL -> java.util.logging.Level.ALL;
|
||||||
|
case DEBUG -> java.util.logging.Level.FINE;
|
||||||
|
case TRACE -> java.util.logging.Level.FINER;
|
||||||
|
case INFO -> java.util.logging.Level.INFO;
|
||||||
|
case WARNING -> java.util.logging.Level.WARNING;
|
||||||
|
case ERROR -> java.util.logging.Level.SEVERE;
|
||||||
|
case OFF -> java.util.logging.Level.OFF;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLoggable(Level level) {
|
||||||
|
return logger.isLoggable(level(level));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void log(Level level, ResourceBundle bundle, String msg, Throwable thrown) {
|
||||||
|
var julLevel = level(level);
|
||||||
|
if (!logger.isLoggable(julLevel)) return;
|
||||||
|
if (bundle != null) {
|
||||||
|
logger.logrb(julLevel, bundle, msg, thrown);
|
||||||
|
} else {
|
||||||
|
logger.log(julLevel, msg, thrown);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void log(Level level, ResourceBundle bundle, String format, Object... params) {
|
||||||
|
var julLevel = level(level);
|
||||||
|
if (!logger.isLoggable(julLevel)) return;
|
||||||
|
if (params == null) {
|
||||||
|
if (bundle == null) {
|
||||||
|
logger.log(julLevel, format);
|
||||||
|
} else {
|
||||||
|
logger.logrb(julLevel, bundle, format);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (bundle == null) {
|
||||||
|
logger.log(julLevel, format, params);
|
||||||
|
} else {
|
||||||
|
logger.logrb(julLevel, bundle, format, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
|||||||
|
loggerfinder.SimpleLoggerFinder
|
@ -0,0 +1,297 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023, 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.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @bug 8314263
|
||||||
|
* @summary Signed jars triggering Logger finder recursion and StackOverflowError
|
||||||
|
* @library /test/lib
|
||||||
|
* @build jdk.test.lib.compiler.CompilerUtils
|
||||||
|
* jdk.test.lib.process.*
|
||||||
|
* jdk.test.lib.util.JarUtils
|
||||||
|
* jdk.test.lib.JDKToolLauncher
|
||||||
|
* @compile SignedLoggerFinderTest.java SimpleLoggerFinder.java
|
||||||
|
* @run main SignedLoggerFinderTest init
|
||||||
|
* @run main SignedLoggerFinderTest init sign
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.file.*;
|
||||||
|
import java.security.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.*;
|
||||||
|
import java.util.jar.*;
|
||||||
|
|
||||||
|
import jdk.test.lib.JDKToolFinder;
|
||||||
|
import jdk.test.lib.JDKToolLauncher;
|
||||||
|
import jdk.test.lib.Utils;
|
||||||
|
import jdk.test.lib.process.OutputAnalyzer;
|
||||||
|
import jdk.test.lib.process.ProcessTools;
|
||||||
|
import jdk.test.lib.util.JarUtils;
|
||||||
|
|
||||||
|
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
|
||||||
|
public class SignedLoggerFinderTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test triggers recursion in the broken JDK. The error can
|
||||||
|
* manifest in a few different ways.
|
||||||
|
* One error seen is "java.lang.NoClassDefFoundError:
|
||||||
|
* Could not initialize class jdk.internal.logger.LoggerFinderLoader$ErrorPolicy"
|
||||||
|
*
|
||||||
|
* The original reported error was a StackOverflow (also seen in different iterations
|
||||||
|
* of this run). Running test in signed and unsigned jar mode for sanity coverage.
|
||||||
|
* The current bug only manifests when jars are signed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
private static boolean init = false;
|
||||||
|
private static boolean signJars = false;
|
||||||
|
private static boolean mutliThreadLoad = false;
|
||||||
|
private static volatile boolean testComplete = false;
|
||||||
|
|
||||||
|
private static final String KEYSTORE = "8314263.keystore";
|
||||||
|
private static final String ALIAS = "JavaTest";
|
||||||
|
private static final String STOREPASS = "changeit";
|
||||||
|
private static final String KEYPASS = "changeit";
|
||||||
|
private static final String DNAME = "CN=sample";
|
||||||
|
private static final String CUSTOM_LOGGER_FINDER_NAME =
|
||||||
|
"loggerfinder.SimpleLoggerFinder";
|
||||||
|
private static final String CUSTOM_LOGGER_NAME =
|
||||||
|
"loggerfinder.SimpleLoggerFinder$SimpleLogger";
|
||||||
|
private static final String INTERNAL_LOGGER_FINDER_NAME =
|
||||||
|
"sun.util.logging.internal.LoggingProviderImpl";
|
||||||
|
private static final String INTERNAL_LOGGER_NAME =
|
||||||
|
"sun.util.logging.internal.LoggingProviderImpl$JULWrapper";
|
||||||
|
private static final Path jarPath1 =
|
||||||
|
Path.of(System.getProperty("test.classes", "."), "SimpleLoggerFinder.jar");
|
||||||
|
private static final Path jarPath2 =
|
||||||
|
Path.of(System.getProperty("test.classes", "."), "SimpleLoggerFinder2.jar");
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Throwable {
|
||||||
|
init = args.length >=1 && args[0].equals("init");
|
||||||
|
signJars = args.length >=2 && args[1].equals("sign");
|
||||||
|
|
||||||
|
// init only passed in by jtreg test run, initialize the environment
|
||||||
|
// for the subsequent test run
|
||||||
|
if (init) {
|
||||||
|
initialize();
|
||||||
|
launchTest(false, false);
|
||||||
|
launchTest(false, true);
|
||||||
|
launchTest(true, false);
|
||||||
|
launchTest(true, true);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// set up complete. Run the code to trigger the recursion
|
||||||
|
// We're in the JVM launched by ProcessTools.executeCommand
|
||||||
|
boolean multiThreadLoad = Boolean.getBoolean("multiThreadLoad");
|
||||||
|
boolean withCustomLoggerFinder = Boolean.getBoolean("withCustomLoggerFinder");
|
||||||
|
|
||||||
|
if (multiThreadLoad) {
|
||||||
|
long sleep = new Random().nextLong(100L) + 1L;
|
||||||
|
System.out.println("multi thread load sleep value: " + sleep);
|
||||||
|
new Thread(runnableWithSleep(
|
||||||
|
() -> System.getLogger("logger" + System.currentTimeMillis()),
|
||||||
|
sleep, "System.getLogger type: ")).start();
|
||||||
|
new Thread(runnableWithSleep(
|
||||||
|
() -> System.LoggerFinder.getLoggerFinder(),
|
||||||
|
sleep, "System.getLoggerFinder type: ")).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (withCustomLoggerFinder) {
|
||||||
|
JarFile jf = new JarFile(jarPath1.toString(), true);
|
||||||
|
jf.getInputStream(jf.getJarEntry("loggerfinder/SimpleLoggerFinder.class"));
|
||||||
|
JarFile jf2 = new JarFile(jarPath2.toString(), true);
|
||||||
|
jf2.getInputStream(jf.getJarEntry("loggerfinder/SimpleLoggerFinder.class"));
|
||||||
|
} else {
|
||||||
|
// some other call to prod LoggerFinder loading
|
||||||
|
System.getLogger("random" + System.currentTimeMillis());
|
||||||
|
System.LoggerFinder.getLoggerFinder();
|
||||||
|
}
|
||||||
|
Security.setProperty("test_1", "test");
|
||||||
|
|
||||||
|
// some extra sanity checks
|
||||||
|
if (withCustomLoggerFinder) {
|
||||||
|
assertEquals(System.LoggerFinder.getLoggerFinder().getClass().getName(),
|
||||||
|
CUSTOM_LOGGER_FINDER_NAME);
|
||||||
|
System.Logger testLogger = System.getLogger("jdk.event.security");
|
||||||
|
assertEquals(testLogger.getClass().getName(), CUSTOM_LOGGER_NAME);
|
||||||
|
} else {
|
||||||
|
assertEquals(System.LoggerFinder.getLoggerFinder().getClass().getName(),
|
||||||
|
INTERNAL_LOGGER_FINDER_NAME);
|
||||||
|
System.Logger testLogger = System.getLogger("jdk.event.security");
|
||||||
|
assertEquals(testLogger.getClass().getName(), INTERNAL_LOGGER_NAME);
|
||||||
|
}
|
||||||
|
testComplete = true;
|
||||||
|
|
||||||
|
// LoggerFinder should be initialized, trigger a simple log call
|
||||||
|
Security.setProperty("test_2", "test");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper to create the inner test. Run config variations with the LoggerFinder jars
|
||||||
|
// on the classpath and with other threads running System.Logger calls during load
|
||||||
|
private static void launchTest(boolean multiThreadLoad, boolean withCustomLoggerFinder) {
|
||||||
|
List<String> cmds = new ArrayList<>();
|
||||||
|
cmds.add(JDKToolFinder.getJDKTool("java"));
|
||||||
|
cmds.addAll(asList(Utils.getTestJavaOpts()));
|
||||||
|
if (withCustomLoggerFinder) {
|
||||||
|
cmds.addAll(List.of("-classpath",
|
||||||
|
System.getProperty("test.classes") + File.pathSeparator +
|
||||||
|
jarPath1.toString() + File.pathSeparator + jarPath2.toString(),
|
||||||
|
"-Dtest.classes=" + System.getProperty("test.classes")));
|
||||||
|
} else {
|
||||||
|
cmds.addAll(List.of("-classpath",
|
||||||
|
System.getProperty("test.classes")));
|
||||||
|
}
|
||||||
|
cmds.addAll(List.of(
|
||||||
|
// following debug property seems useful to tickle the issue
|
||||||
|
"-Dsun.misc.URLClassPath.debug=true",
|
||||||
|
// console logger level to capture event output
|
||||||
|
"-Djdk.system.logger.level=DEBUG",
|
||||||
|
// useful for debug purposes
|
||||||
|
"-Djdk.logger.finder.error=DEBUG",
|
||||||
|
// enable logging to verify correct output
|
||||||
|
"-Djava.util.logging.config.file=" +
|
||||||
|
Path.of(System.getProperty("test.src", "."), "logging.properties")));
|
||||||
|
if (multiThreadLoad) {
|
||||||
|
cmds.add("-DmultiThreadLoad=true");
|
||||||
|
}
|
||||||
|
if (withCustomLoggerFinder) {
|
||||||
|
cmds.add("-DwithCustomLoggerFinder=true");
|
||||||
|
}
|
||||||
|
cmds.addAll(List.of(
|
||||||
|
"SignedLoggerFinderTest",
|
||||||
|
"no-init"));
|
||||||
|
|
||||||
|
try {
|
||||||
|
OutputAnalyzer outputAnalyzer = ProcessTools.executeCommand(cmds.stream()
|
||||||
|
.filter(t -> !t.isEmpty())
|
||||||
|
.toArray(String[]::new))
|
||||||
|
.shouldHaveExitValue(0);
|
||||||
|
if (withCustomLoggerFinder) {
|
||||||
|
outputAnalyzer
|
||||||
|
.shouldContain("TEST LOGGER: [test_1, test]")
|
||||||
|
.shouldContain("TEST LOGGER: [test_2, test]");
|
||||||
|
} else {
|
||||||
|
outputAnalyzer
|
||||||
|
.shouldContain("SecurityPropertyModification: key:test_1")
|
||||||
|
.shouldContain("SecurityPropertyModification: key:test_2");
|
||||||
|
}
|
||||||
|
if (withCustomLoggerFinder && signJars) {
|
||||||
|
// X509 cert generated during verification of signed jar file
|
||||||
|
outputAnalyzer
|
||||||
|
.shouldContain(DNAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Throwable t) {
|
||||||
|
throw new RuntimeException("Unexpected fail.", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Runnable runnableWithSleep(Supplier s, long sleep, String desc) {
|
||||||
|
return () -> {
|
||||||
|
while(!testComplete) {
|
||||||
|
System.out.println(desc + s.get().getClass().getName());
|
||||||
|
try {
|
||||||
|
Thread.sleep(sleep);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void initialize() throws Throwable {
|
||||||
|
if (signJars) {
|
||||||
|
genKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
Path classes = Paths.get(System.getProperty("test.classes", ""));
|
||||||
|
JarUtils.createJarFile(jarPath1,
|
||||||
|
classes,
|
||||||
|
classes.resolve("loggerfinder/SimpleLoggerFinder.class"),
|
||||||
|
classes.resolve("loggerfinder/SimpleLoggerFinder$SimpleLogger.class"));
|
||||||
|
|
||||||
|
JarUtils.updateJarFile(jarPath1, Path.of(System.getProperty("test.src")),
|
||||||
|
Path.of("META-INF", "services", "java.lang.System$LoggerFinder"));
|
||||||
|
if (signJars) {
|
||||||
|
signJar(jarPath1.toString());
|
||||||
|
}
|
||||||
|
// multiple signed jars with services to have ServiceLoader iteration
|
||||||
|
Files.copy(jarPath1, jarPath2, REPLACE_EXISTING);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void genKey() throws Throwable {
|
||||||
|
String keytool = JDKToolFinder.getJDKTool("keytool");
|
||||||
|
Files.deleteIfExists(Paths.get(KEYSTORE));
|
||||||
|
ProcessTools.executeCommand(keytool,
|
||||||
|
"-J-Duser.language=en",
|
||||||
|
"-J-Duser.country=US",
|
||||||
|
"-genkey",
|
||||||
|
"-keyalg", "rsa",
|
||||||
|
"-alias", ALIAS,
|
||||||
|
"-keystore", KEYSTORE,
|
||||||
|
"-keypass", KEYPASS,
|
||||||
|
"-dname", DNAME,
|
||||||
|
"-storepass", STOREPASS
|
||||||
|
).shouldHaveExitValue(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static OutputAnalyzer signJar(String jarName) throws Throwable {
|
||||||
|
List<String> args = new ArrayList<>();
|
||||||
|
args.add("-verbose");
|
||||||
|
args.add(jarName);
|
||||||
|
args.add(ALIAS);
|
||||||
|
|
||||||
|
return jarsigner(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static OutputAnalyzer jarsigner(List<String> extra)
|
||||||
|
throws Throwable {
|
||||||
|
JDKToolLauncher launcher = JDKToolLauncher.createUsingTestJDK("jarsigner")
|
||||||
|
.addVMArg("-Duser.language=en")
|
||||||
|
.addVMArg("-Duser.country=US")
|
||||||
|
.addToolArg("-keystore")
|
||||||
|
.addToolArg(KEYSTORE)
|
||||||
|
.addToolArg("-storepass")
|
||||||
|
.addToolArg(STOREPASS)
|
||||||
|
.addToolArg("-keypass")
|
||||||
|
.addToolArg(KEYPASS);
|
||||||
|
for (String s : extra) {
|
||||||
|
if (s.startsWith("-J")) {
|
||||||
|
launcher.addVMArg(s.substring(2));
|
||||||
|
} else {
|
||||||
|
launcher.addToolArg(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ProcessTools.executeCommand(launcher.getCommand());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertEquals(String received, String expected) {
|
||||||
|
if (!expected.equals(received)) {
|
||||||
|
throw new RuntimeException("Received: " + received);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023, 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.
|
||||||
|
*
|
||||||
|
* 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 loggerfinder;
|
||||||
|
|
||||||
|
import java.lang.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class SimpleLoggerFinder extends System.LoggerFinder {
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
long sleep = new Random().nextLong(1000L) + 1L;
|
||||||
|
System.out.println("Logger finder service load sleep value: " + sleep);
|
||||||
|
// simulate a slow load service
|
||||||
|
Thread.sleep(sleep);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public System.Logger getLogger(String name, Module module) {
|
||||||
|
return new SimpleLogger(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class SimpleLogger implements System.Logger {
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
public SimpleLogger(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLoggable(Level level) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void log(Level level, ResourceBundle bundle, String msg, Throwable thrown) {
|
||||||
|
System.out.println("TEST LOGGER: " + msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void log(Level level, ResourceBundle bundle, String format, Object... params) {
|
||||||
|
System.out.println("TEST LOGGER: " + Arrays.asList(params));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
############################################################
|
||||||
|
# Configuration file for log testing
|
||||||
|
#
|
||||||
|
############################################################
|
||||||
|
|
||||||
|
handlers= java.util.logging.ConsoleHandler
|
||||||
|
|
||||||
|
.level= FINE
|
||||||
|
|
||||||
|
java.util.logging.ConsoleHandler.level = FINE
|
||||||
|
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
|
||||||
|
|
||||||
|
jdk.event.security.level = FINE
|
||||||
|
|
Loading…
Reference in New Issue
Block a user