changeset 13339:117b25ef4717

8145686: SimpleConsoleLogger and LogRecord should take advantage of StackWalker to skip classes implementing System.Logger Summary: methods defined on classes implementing System.Logger will be skipped when looking for the calling method. Reviewed-by: mchung
author dfuchs
date Mon, 21 Dec 2015 13:30:58 +0100
parents 870441a8890a
children 7c8488605485
files src/java.base/share/classes/jdk/internal/logger/SimpleConsoleLogger.java src/java.logging/share/classes/java/util/logging/LogRecord.java test/java/lang/System/Logger/default/DefaultLoggerTest.java test/java/lang/System/LoggerFinder/internal/BaseDefaultLoggerFinderTest/BaseDefaultLoggerFinderTest.java
diffstat 4 files changed, 456 insertions(+), 29 deletions(-) [+]
line wrap: on
line diff
--- a/src/java.base/share/classes/jdk/internal/logger/SimpleConsoleLogger.java	Mon Dec 21 13:43:53 2015 +0800
+++ b/src/java.base/share/classes/jdk/internal/logger/SimpleConsoleLogger.java	Mon Dec 21 13:30:58 2015 +0100
@@ -28,6 +28,7 @@
 import java.io.PrintStream;
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.lang.StackWalker.StackFrame;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
 import java.time.ZonedDateTime;
@@ -180,7 +181,16 @@
      * CallerFinder is a stateful predicate.
      */
     static final class CallerFinder implements Predicate<StackWalker.StackFrame> {
-        static final StackWalker WALKER = StackWalker.getInstance();
+        private static final StackWalker WALKER;
+        static {
+            final PrivilegedAction<StackWalker> action = new PrivilegedAction<>() {
+                @Override
+                public StackWalker run() {
+                    return StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
+                }
+            };
+            WALKER = AccessController.doPrivileged(action);
+        }
 
         /**
          * Returns StackFrame of the caller's frame.
@@ -210,8 +220,9 @@
                 lookingForLogger = !isLoggerImplFrame(cname);
                 return false;
             }
-            // We've found the relevant frame.
-            return !skipLoggingFrame(cname) && !isLoggerImplFrame(cname);
+            // Continue walking until we've found the relevant calling frame.
+            // Skips logging/logger infrastructure.
+            return !isFilteredFrame(t);
         }
 
         private boolean isLoggerImplFrame(String cname) {
@@ -281,8 +292,8 @@
         return Formatting.getSimpleFormat(defaultPropertyGetter);
     }
 
-    public static boolean skipLoggingFrame(String cname) {
-        return Formatting.skipLoggingFrame(cname);
+    public static boolean isFilteredFrame(StackFrame st) {
+        return Formatting.isFilteredFrame(st);
     }
 
     @Override
@@ -393,16 +404,19 @@
 
         }
 
-        static boolean skipLoggingFrame(String cname) {
+        static boolean isFilteredFrame(StackFrame st) {
             // skip logging/logger infrastructure
+            if (System.Logger.class.isAssignableFrom(st.getDeclaringClass())) {
+                return true;
+            }
 
             // fast escape path: all the prefixes below start with 's' or 'j' and
             // have more than 12 characters.
+            final String cname = st.getClassName();
             char c = cname.length() < 12 ? 0 : cname.charAt(0);
             if (c == 's') {
                 // skip internal machinery classes
                 if (cname.startsWith("sun.util.logging."))   return true;
-                if (cname.startsWith("sun.reflect."))        return true;
                 if (cname.startsWith("sun.rmi.runtime.Log")) return true;
             } else if (c == 'j') {
                 // Message delayed at Bootstrap: no need to go further up.
@@ -410,10 +424,7 @@
                 // skip public machinery classes
                 if (cname.startsWith("jdk.internal.logger."))          return true;
                 if (cname.startsWith("java.util.logging."))            return true;
-                if (cname.startsWith("java.lang.System$Logger"))       return true;
-                if (cname.startsWith("java.lang.reflect."))            return true;
                 if (cname.startsWith("java.lang.invoke.MethodHandle")) return true;
-                if (cname.startsWith("java.lang.invoke.LambdaForm"))    return true;
                 if (cname.startsWith("java.security.AccessController")) return true;
             }
 
--- a/src/java.logging/share/classes/java/util/logging/LogRecord.java	Mon Dec 21 13:43:53 2015 +0800
+++ b/src/java.logging/share/classes/java/util/logging/LogRecord.java	Mon Dec 21 13:30:58 2015 +0100
@@ -29,10 +29,12 @@
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 import java.io.*;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
 import java.time.Clock;
 import java.util.function.Predicate;
 
-import static jdk.internal.logger.SimpleConsoleLogger.skipLoggingFrame;
+import static jdk.internal.logger.SimpleConsoleLogger.isFilteredFrame;
 
 /**
  * LogRecord objects are used to pass logging requests between
@@ -685,7 +687,12 @@
      * CallerFinder is a stateful predicate.
      */
     static final class CallerFinder implements Predicate<StackWalker.StackFrame> {
-        static final StackWalker WALKER = StackWalker.getInstance();
+        private static final StackWalker WALKER;
+        static {
+            final PrivilegedAction<StackWalker> action =
+                    () -> StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
+            WALKER = AccessController.doPrivileged(action);
+        }
 
         /**
          * Returns StackFrame of the caller's frame.
@@ -715,8 +722,9 @@
                 lookingForLogger = !isLoggerImplFrame(cname);
                 return false;
             }
-            // skip logging/logger infrastructure and reflection calls
-            return !skipLoggingFrame(cname);
+            // Continue walking until we've found the relevant calling frame.
+            // Skips logging/logger infrastructure.
+            return !isFilteredFrame(t);
         }
 
         private boolean isLoggerImplFrame(String cname) {
--- a/test/java/lang/System/Logger/default/DefaultLoggerTest.java	Mon Dec 21 13:43:53 2015 +0800
+++ b/test/java/lang/System/Logger/default/DefaultLoggerTest.java	Mon Dec 21 13:30:58 2015 +0100
@@ -43,13 +43,16 @@
 import java.lang.System.LoggerFinder;
 import java.lang.System.Logger;
 import java.lang.System.Logger.Level;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.function.Function;
 import java.util.logging.Handler;
 import java.util.logging.LogRecord;
 import java.util.stream.Stream;
 
 /**
  * @test
- * @bug     8140364
+ * @bug     8140364 8145686
  * @summary Tests default loggers returned by System.getLogger, and in
  *          particular the implementation of the the System.Logger method
  *          performed by the default binding.
@@ -59,6 +62,8 @@
  * @run main/othervm -Xbootclasspath/a:boot DefaultLoggerTest NOSECURITY
  * @run main/othervm -Xbootclasspath/a:boot DefaultLoggerTest NOPERMISSIONS
  * @run main/othervm -Xbootclasspath/a:boot DefaultLoggerTest WITHPERMISSIONS
+ * @run main/othervm -Xbootclasspath/a:boot DefaultLoggerTest WITHCUSTOMWRAPPERS
+ * @run main/othervm -Xbootclasspath/a:boot DefaultLoggerTest WITHREFLECTION
  * @author danielfuchs
  */
 public class DefaultLoggerTest {
@@ -232,7 +237,8 @@
 
     static final AccessSystemLogger accessSystemLogger = new AccessSystemLogger();
 
-    static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS};
+    static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS,
+            WITHCUSTOMWRAPPERS, WITHREFLECTION};
 
     static void setSecurityManager() {
         if (System.getSecurityManager() == null) {
@@ -240,12 +246,179 @@
             System.setSecurityManager(new SecurityManager());
         }
     }
+
+    /**
+     * The CustomLoggerWrapper makes it possible to verify that classes
+     * which implements System.Logger will be skipped when looking for
+     * the calling method.
+     */
+    static class CustomLoggerWrapper implements Logger {
+
+        Logger impl;
+        public CustomLoggerWrapper(Logger logger) {
+            this.impl = Objects.requireNonNull(logger);
+        }
+
+
+        @Override
+        public String getName() {
+            return impl.getName();
+        }
+
+        @Override
+        public boolean isLoggable(Level level) {
+            return impl.isLoggable(level);
+        }
+
+        @Override
+        public void log(Level level, ResourceBundle rb, String string, Throwable thrwbl) {
+            impl.log(level, rb, string, thrwbl);
+        }
+
+        @Override
+        public void log(Level level, ResourceBundle rb, String string, Object... os) {
+            impl.log(level, rb, string, os);
+        }
+
+        @Override
+        public void log(Level level, Object o) {
+            impl.log(level, o);
+        }
+
+        @Override
+        public void log(Level level, String string) {
+            impl.log(level, string);
+        }
+
+        @Override
+        public void log(Level level, Supplier<String> splr) {
+            impl.log(level, splr);
+        }
+
+        @Override
+        public void log(Level level, String string, Object... os) {
+           impl.log(level, string, os);
+        }
+
+        @Override
+        public void log(Level level, String string, Throwable thrwbl) {
+            impl.log(level, string, thrwbl);
+        }
+
+        @Override
+        public void log(Level level, Supplier<String> splr, Throwable thrwbl) {
+            Logger.super.log(level, splr, thrwbl);
+        }
+
+        @Override
+        public String toString() {
+            return super.toString() + "(impl=" + impl + ")";
+        }
+
+    }
+
+    /**
+     * The ReflectionLoggerWrapper additionally makes it possible to verify
+     * that code which use reflection to call System.Logger will be skipped
+     * when looking for the calling method.
+     */
+    static class ReflectionLoggerWrapper implements Logger {
+
+        Logger impl;
+        public ReflectionLoggerWrapper(Logger logger) {
+            this.impl = Objects.requireNonNull(logger);
+        }
+
+        private Object invoke(Method m, Object... params) {
+            try {
+                return m.invoke(impl, params);
+            } catch (IllegalAccessException | IllegalArgumentException
+                    | InvocationTargetException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+
+        @Override
+        public String getName() {
+            return impl.getName();
+        }
+
+        @Override
+        public boolean isLoggable(Level level) {
+            return impl.isLoggable(level);
+        }
+
+        @Override
+        public void log(Level level, ResourceBundle rb, String string, Throwable thrwbl) {
+            try {
+                invoke(System.Logger.class.getMethod(
+                        "log", Level.class, ResourceBundle.class, String.class, Throwable.class),
+                        level, rb, string, thrwbl);
+            } catch (NoSuchMethodException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+
+        @Override
+        public void log(Level level, ResourceBundle rb, String string, Object... os) {
+            try {
+                invoke(System.Logger.class.getMethod(
+                        "log", Level.class, ResourceBundle.class, String.class, Object[].class),
+                        level, rb, string, os);
+            } catch (NoSuchMethodException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+
+        @Override
+        public void log(Level level, String string) {
+            try {
+                invoke(System.Logger.class.getMethod(
+                        "log", Level.class, String.class),
+                        level, string);
+            } catch (NoSuchMethodException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+
+        @Override
+        public void log(Level level, String string, Object... os) {
+            try {
+                invoke(System.Logger.class.getMethod(
+                        "log", Level.class, String.class, Object[].class),
+                        level, string, os);
+            } catch (NoSuchMethodException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+
+        @Override
+        public void log(Level level, String string, Throwable thrwbl) {
+            try {
+                invoke(System.Logger.class.getMethod(
+                        "log", Level.class, String.class, Throwable.class),
+                        level, string, thrwbl);
+            } catch (NoSuchMethodException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+
+
+        @Override
+        public String toString() {
+            return super.toString() + "(impl=" + impl + ")";
+        }
+
+    }
+
     public static void main(String[] args) {
         if (args.length == 0)
             args = new String[] {
                 "NOSECURITY",
                 "NOPERMISSIONS",
-                "WITHPERMISSIONS"
+                "WITHPERMISSIONS",
+                "WITHCUSTOMWRAPPERS",
+                "WITHREFLECTION"
             };
 
         // 1. Obtain destination loggers directly from the LoggerFinder
@@ -276,6 +449,31 @@
                         allowControl.get().set(control);
                     }
                     break;
+                case WITHCUSTOMWRAPPERS:
+                    System.out.println("\n*** With Security Manager, with control permission, using custom Wrappers\n");
+                    setSecurityManager();
+                    final boolean previous = allowControl.get().get();
+                    try {
+                        allowControl.get().set(true);
+                        test(CustomLoggerWrapper::new, true);
+                    } finally {
+                        allowControl.get().set(previous);
+                    }
+                    break;
+                case WITHREFLECTION:
+                    System.out.println("\n*** With Security Manager,"
+                            + " with control permission,"
+                            + " using reflection while logging\n");
+                    setSecurityManager();
+                    final boolean before = allowControl.get().get();
+                    try {
+                        allowControl.get().set(true);
+                        test(ReflectionLoggerWrapper::new, true);
+                    } finally {
+                        allowControl.get().set(before);
+                    }
+                    break;
+
                 default:
                     throw new RuntimeException("Unknown test case: " + testCase);
             }
@@ -284,6 +482,10 @@
     }
 
     public static void test(boolean hasRequiredPermissions) {
+        test(Function.identity(), hasRequiredPermissions);
+    }
+
+    public static void test(Function<Logger, Logger> wrapper, boolean hasRequiredPermissions) {
 
         ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName());
         final Map<Logger, String> loggerDescMap = new HashMap<>();
@@ -294,7 +496,7 @@
         //   - and AccessSystemLogger.getLogger("foo")
         Logger sysLogger1 = null;
         try {
-            sysLogger1 = accessSystemLogger.getLogger("foo");
+            sysLogger1 = wrapper.apply(accessSystemLogger.getLogger("foo"));
             loggerDescMap.put(sysLogger1, "AccessSystemLogger.getLogger(\"foo\")");
         } catch (AccessControlException acx) {
             if (hasRequiredPermissions) {
@@ -306,7 +508,7 @@
             throw new RuntimeException("unexpected exception: " + acx, acx);
         }
 
-        Logger appLogger1 = System.getLogger("foo");
+        Logger appLogger1 = wrapper.apply(System.getLogger("foo"));
         loggerDescMap.put(appLogger1, "System.getLogger(\"foo\");");
 
         if (appLogger1 == sysLogger1) {
@@ -316,13 +518,13 @@
         // 2. Test loggers returned by:
         //   - System.getLogger(\"foo\", loggerBundle)
         //   - and AccessSystemLogger.getLogger(\"foo\", loggerBundle)
-        Logger appLogger2 =
-                System.getLogger("foo", loggerBundle);
+        Logger appLogger2 = wrapper.apply(
+                System.getLogger("foo", loggerBundle));
         loggerDescMap.put(appLogger2, "System.getLogger(\"foo\", loggerBundle)");
 
         Logger sysLogger2 = null;
         try {
-            sysLogger2 = accessSystemLogger.getLogger("foo", loggerBundle);
+            sysLogger2 = wrapper.apply(accessSystemLogger.getLogger("foo", loggerBundle));
             loggerDescMap.put(sysLogger2, "AccessSystemLogger.getLogger(\"foo\", loggerBundle)");
         } catch (AccessControlException acx) {
             if (hasRequiredPermissions) {
--- a/test/java/lang/System/LoggerFinder/internal/BaseDefaultLoggerFinderTest/BaseDefaultLoggerFinderTest.java	Mon Dec 21 13:43:53 2015 +0800
+++ b/test/java/lang/System/LoggerFinder/internal/BaseDefaultLoggerFinderTest/BaseDefaultLoggerFinderTest.java	Mon Dec 21 13:30:58 2015 +0100
@@ -44,17 +44,21 @@
 import java.lang.System.LoggerFinder;
 import java.lang.System.Logger;
 import java.lang.System.Logger.Level;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
 import java.util.Locale;
+import java.util.Objects;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Function;
 import jdk.internal.logger.DefaultLoggerFinder;
 import jdk.internal.logger.SimpleConsoleLogger;
 import sun.util.logging.PlatformLogger;
 
 /**
  * @test
- * @bug     8140364
+ * @bug     8140364 8145686
  * @summary JDK implementation specific unit test for the base DefaultLoggerFinder.
  *          Tests the behavior of DefaultLoggerFinder and SimpleConsoleLogger
  *          implementation.
@@ -65,6 +69,8 @@
  * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader BaseDefaultLoggerFinderTest NOSECURITY
  * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader BaseDefaultLoggerFinderTest NOPERMISSIONS
  * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader BaseDefaultLoggerFinderTest WITHPERMISSIONS
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader BaseDefaultLoggerFinderTest WITHCUSTOMWRAPPERS
+ * @run  main/othervm -Xbootclasspath/a:boot -Djava.system.class.loader=CustomSystemClassLoader BaseDefaultLoggerFinderTest WITHREFLECTION
  * @author danielfuchs
  */
 public class BaseDefaultLoggerFinderTest {
@@ -172,7 +178,8 @@
 
     }
 
-    static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS};
+    static enum TestCases {NOSECURITY, NOPERMISSIONS, WITHPERMISSIONS,
+                           WITHCUSTOMWRAPPERS, WITHREFLECTION};
 
     static void setSecurityManager() {
         if (System.getSecurityManager() == null) {
@@ -261,12 +268,173 @@
         return b.append(name).append("=").append(value).append('\n');
     }
 
+    static class CustomLoggerWrapper implements Logger {
+
+        Logger impl;
+        public CustomLoggerWrapper(Logger logger) {
+            this.impl = Objects.requireNonNull(logger);
+        }
+
+
+        @Override
+        public String getName() {
+            return impl.getName();
+        }
+
+        @Override
+        public boolean isLoggable(Level level) {
+            return impl.isLoggable(level);
+        }
+
+        @Override
+        public void log(Level level, ResourceBundle rb, String string, Throwable thrwbl) {
+            impl.log(level, rb, string, thrwbl);
+        }
+
+        @Override
+        public void log(Level level, ResourceBundle rb, String string, Object... os) {
+            impl.log(level, rb, string, os);
+        }
+
+        @Override
+        public void log(Level level, Object o) {
+            impl.log(level, o);
+        }
+
+        @Override
+        public void log(Level level, String string) {
+            impl.log(level, string);
+        }
+
+        @Override
+        public void log(Level level, Supplier<String> splr) {
+            impl.log(level, splr);
+        }
+
+        @Override
+        public void log(Level level, String string, Object... os) {
+           impl.log(level, string, os);
+        }
+
+        @Override
+        public void log(Level level, String string, Throwable thrwbl) {
+            impl.log(level, string, thrwbl);
+        }
+
+        @Override
+        public void log(Level level, Supplier<String> splr, Throwable thrwbl) {
+            Logger.super.log(level, splr, thrwbl);
+        }
+
+        @Override
+        public String toString() {
+            return super.toString() + "(impl=" + impl + ")";
+        }
+
+    }
+    /**
+     * The ReflectionLoggerWrapper additionally makes it possible to verify
+     * that code which use reflection to call System.Logger will be skipped
+     * when looking for the calling method.
+     */
+    static class ReflectionLoggerWrapper implements Logger {
+
+        Logger impl;
+        public ReflectionLoggerWrapper(Logger logger) {
+            this.impl = Objects.requireNonNull(logger);
+        }
+
+        private Object invoke(Method m, Object... params) {
+            try {
+                return m.invoke(impl, params);
+            } catch (IllegalAccessException | IllegalArgumentException
+                    | InvocationTargetException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+
+        @Override
+        public String getName() {
+            return impl.getName();
+        }
+
+        @Override
+        public boolean isLoggable(Level level) {
+            return impl.isLoggable(level);
+        }
+
+        @Override
+        public void log(Level level, ResourceBundle rb, String string, Throwable thrwbl) {
+            try {
+                invoke(System.Logger.class.getMethod(
+                        "log", Level.class, ResourceBundle.class, String.class, Throwable.class),
+                        level, rb, string, thrwbl);
+            } catch (NoSuchMethodException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+
+        @Override
+        public void log(Level level, ResourceBundle rb, String string, Object... os) {
+            try {
+                invoke(System.Logger.class.getMethod(
+                        "log", Level.class, ResourceBundle.class, String.class, Object[].class),
+                        level, rb, string, os);
+            } catch (NoSuchMethodException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+
+        @Override
+        public void log(Level level, String string) {
+            try {
+                invoke(System.Logger.class.getMethod(
+                        "log", Level.class, String.class),
+                        level, string);
+            } catch (NoSuchMethodException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+
+        @Override
+        public void log(Level level, String string, Object... os) {
+            try {
+                invoke(System.Logger.class.getMethod(
+                        "log", Level.class, String.class, Object[].class),
+                        level, string, os);
+            } catch (NoSuchMethodException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+
+        @Override
+        public void log(Level level, String string, Throwable thrwbl) {
+            try {
+                invoke(System.Logger.class.getMethod(
+                        "log", Level.class, String.class, Throwable.class),
+                        level, string, thrwbl);
+            } catch (NoSuchMethodException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+
+
+        @Override
+        public String toString() {
+            return super.toString() + "(impl=" + impl + ")";
+        }
+
+    }
+
+
     public static void main(String[] args) {
         if (args.length == 0) {
             args = new String[] {
                 //"NOSECURITY",
                 "NOPERMISSIONS",
-                "WITHPERMISSIONS"
+                "WITHPERMISSIONS",
+                "WITHCUSTOMWRAPPERS",
+                "WITHREFLECTION"
             };
         }
         Locale.setDefault(Locale.ENGLISH);
@@ -355,6 +523,40 @@
                         allowControl.get().set(control);
                     }
                     break;
+                case WITHCUSTOMWRAPPERS:
+                    System.out.println("\n*** With Security Manager, with control permission and custom Wrapper\n");
+                    System.out.println(TestLoggerFinder.conf.get());
+                    setSecurityManager();
+                    final boolean previous = allowControl.get().get();
+                    try {
+                        allowControl.get().set(true);
+                        provider = getLoggerFinder(expectedClass);
+                        if (!provider.getClass().getName().equals("BaseDefaultLoggerFinderTest$BaseLoggerFinder")) {
+                            throw new RuntimeException("Unexpected provider: " + provider.getClass().getName());
+                        }
+                        test(provider, CustomLoggerWrapper::new, true);
+                    } finally {
+                        allowControl.get().set(previous);
+                    }
+                    break;
+                case WITHREFLECTION:
+                    System.out.println("\n*** With Security Manager,"
+                            + " with control permission,"
+                            + " using reflection while logging\n");
+                    System.out.println(TestLoggerFinder.conf.get());
+                    setSecurityManager();
+                    final boolean before = allowControl.get().get();
+                    try {
+                        allowControl.get().set(true);
+                        provider = getLoggerFinder(expectedClass);
+                        if (!provider.getClass().getName().equals("BaseDefaultLoggerFinderTest$BaseLoggerFinder")) {
+                            throw new RuntimeException("Unexpected provider: " + provider.getClass().getName());
+                        }
+                        test(provider, ReflectionLoggerWrapper::new, true);
+                    } finally {
+                        allowControl.get().set(before);
+                    }
+                    break;
                 default:
                     throw new RuntimeException("Unknown test case: " + testCase);
             }
@@ -363,17 +565,21 @@
     }
 
     public static void test(TestLoggerFinder provider, boolean hasRequiredPermissions) {
+        test(provider, Function.identity(), hasRequiredPermissions);
+    }
+
+    public static void test(TestLoggerFinder provider, Function<Logger, Logger> wrapper, boolean hasRequiredPermissions) {
 
         ResourceBundle loggerBundle = ResourceBundle.getBundle(MyLoggerBundle.class.getName());
         final Map<Logger, String> loggerDescMap = new HashMap<>();
 
-        System.Logger sysLogger = accessSystemLogger.getLogger("foo");
+        System.Logger sysLogger = wrapper.apply(accessSystemLogger.getLogger("foo"));
         loggerDescMap.put(sysLogger, "accessSystemLogger.getLogger(\"foo\")");
-        System.Logger localizedSysLogger = accessSystemLogger.getLogger("fox", loggerBundle);
+        System.Logger localizedSysLogger = wrapper.apply(accessSystemLogger.getLogger("fox", loggerBundle));
         loggerDescMap.put(localizedSysLogger, "accessSystemLogger.getLogger(\"fox\", loggerBundle)");
-        System.Logger appLogger = System.getLogger("bar");
+        System.Logger appLogger = wrapper.apply(System.getLogger("bar"));
         loggerDescMap.put(appLogger,"System.getLogger(\"bar\")");
-        System.Logger localizedAppLogger = System.getLogger("baz", loggerBundle);
+        System.Logger localizedAppLogger = wrapper.apply(System.getLogger("baz", loggerBundle));
         loggerDescMap.put(localizedAppLogger,"System.getLogger(\"baz\", loggerBundle)");
 
         testLogger(provider, loggerDescMap, "foo", null, sysLogger, accessSystemLogger.getClass());