< prev index next >

src/java.base/share/classes/jdk/internal/module/IllegalAccessLogger.java

Print this page




   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.internal.module;
  27 

  28 import java.lang.invoke.MethodHandles;
  29 import java.lang.reflect.Module;
  30 import java.net.URL;
  31 import java.security.AccessController;
  32 import java.security.CodeSource;
  33 import java.security.PrivilegedAction;
  34 import java.security.ProtectionDomain;
  35 import java.util.HashMap;
  36 import java.util.HashSet;
  37 import java.util.List;
  38 import java.util.Map;
  39 import java.util.Objects;
  40 import java.util.Set;
  41 import java.util.WeakHashMap;
  42 import java.util.function.Supplier;
  43 import java.util.stream.Collectors;
  44 



  45 /**
  46  * Supports logging of access to members of API packages that are exported or
  47  * opened via backdoor mechanisms to code in unnamed modules.
  48  */
  49 
  50 public final class IllegalAccessLogger {
  51 
  52     // true to print stack trace
  53     private static final boolean PRINT_STACK_TRACE;






  54     static {
  55         String s = System.getProperty("sun.reflect.debugModuleAccessChecks");
  56         PRINT_STACK_TRACE = "access".equals(s);






  57     }
  58 
  59     private static final StackWalker STACK_WALKER
  60         = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
  61 
  62     // the maximum number of frames to capture
  63     private static final int MAX_STACK_FRAMES = 32;
  64 
  65     // lock to avoid interference when printing stack traces
  66     private static final Object OUTPUT_LOCK = new Object();
  67 
  68     // caller -> usages
  69     private final Map<Class<?>, Set<Usage>> callerToUsages = new WeakHashMap<>();
  70 
  71     // module -> (package name -> CLI option)
  72     private final Map<Module, Map<String, String>> exported;
  73     private final Map<Module, Map<String, String>> opened;
  74 



  75     private IllegalAccessLogger(Map<Module, Map<String, String>> exported,
  76                                 Map<Module, Map<String, String>> opened) {

  77         this.exported = deepCopy(exported);
  78         this.opened = deepCopy(opened);

  79     }
  80 
  81     /**
  82      * Returns that a Builder that is seeded with the packages known to this logger.
  83      */
  84     public Builder toBuilder() {
  85         return new Builder(exported, opened);
  86     }
  87 
  88     /**
  89      * Logs access to the member of a target class by a caller class if the class
  90      * is in a package that is exported via a backdoor mechanism.
  91      *
  92      * The {@code whatSupplier} supplies the message that describes the member.
  93      */
  94     public void logIfExportedByBackdoor(Class<?> caller,
  95                                         Class<?> target,
  96                                         Supplier<String> whatSupplier) {
  97         Map<String, String> packages = exported.get(target.getModule());
  98         if (packages != null) {


 151             if (how != null) {
 152                 log(caller.lookupClass(), target.getName(), () ->
 153                     "WARNING: Illegal access using Lookup on " + caller.lookupClass()
 154                     + " to " + target + " (permitted by " + how + ")");
 155             }
 156         }
 157     }
 158 
 159     /**
 160      * Log access by a caller. The {@code what} parameter describes the class or
 161      * member that is being accessed. The {@code msgSupplier} supplies the log
 162      * message.
 163      *
 164      * To reduce output, this method only logs the access if it hasn't been seen
 165      * previously. "Seen previously" is implemented as a map of caller class -> Usage,
 166      * where a Usage is the "what" and a hash of the stack trace. The map has weak
 167      * keys so it can be expunged when the caller is GC'ed/unloaded.
 168      */
 169     private void log(Class<?> caller, String what, Supplier<String> msgSupplier) {
 170         // stack trace without the top-most frames in java.base
 171         List<StackWalker.StackFrame> stack = STACK_WALKER.walk(s ->
 172             s.dropWhile(this::isJavaBase)
 173              .limit(MAX_STACK_FRAMES)
 174              .collect(Collectors.toList())
 175         );
 176 
 177         // check if the access has already been recorded
 178         Usage u = new Usage(what, hash(stack));
 179         boolean firstUsage;
 180         synchronized (this) {
 181             firstUsage = callerToUsages.computeIfAbsent(caller, k -> new HashSet<>()).add(u);
 182         }
 183 
 184         // log message if first usage
 185         if (firstUsage) {
 186             String msg = msgSupplier.get();
 187             if (PRINT_STACK_TRACE) {
 188                 synchronized (OUTPUT_LOCK) {
 189                     System.err.println(msg);
 190                     stack.forEach(f -> System.err.println("\tat " + f));
 191                 }
 192             } else {
 193                 System.err.println(msg);
 194             }
 195         }
 196     }
 197 
 198     private static class Usage {
 199         private final String what;
 200         private final int stack;
 201         Usage(String what, int stack) {
 202             this.what = what;
 203             this.stack = stack;
 204         }
 205         @Override
 206         public int hashCode() {
 207             return what.hashCode() ^ stack;
 208         }
 209         @Override
 210         public boolean equals(Object ob) {
 211             if (ob instanceof Usage) {
 212                 Usage that = (Usage)ob;
 213                 return what.equals(that.what) && stack == (that.stack);


 248     public static void setIllegalAccessLogger(IllegalAccessLogger l) {
 249         if (l.exported.isEmpty() && l.opened.isEmpty()) {
 250             logger = null;
 251         } else {
 252             logger = l;
 253         }
 254     }
 255 
 256     /**
 257      * Returns the system-wide IllegalAccessLogger or {@code null} if there is
 258      * no logger.
 259      */
 260     public static IllegalAccessLogger illegalAccessLogger() {
 261         return logger;
 262     }
 263 
 264     /**
 265      * A builder for IllegalAccessLogger objects.
 266      */
 267     public static class Builder {

 268         private Map<Module, Map<String, String>> exported;
 269         private Map<Module, Map<String, String>> opened;

 270 
 271         public Builder() { }
 272 
 273         public Builder(Map<Module, Map<String, String>> exported,
 274                        Map<Module, Map<String, String>> opened) {
 275             this.exported = deepCopy(exported);
 276             this.opened = deepCopy(opened);
 277         }
 278 
 279         public void logAccessToExportedPackage(Module m, String pn, String how) {
 280             if (!m.isExported(pn)) {
 281                 if (exported == null)
 282                     exported = new HashMap<>();
 283                 exported.computeIfAbsent(m, k -> new HashMap<>()).putIfAbsent(pn, how);
 284             }

 285         }
 286 
 287         public void logAccessToOpenPackage(Module m, String pn, String how) {
 288             // opens implies exported at run-time.
 289             logAccessToExportedPackage(m, pn, how);
 290 
 291             if (!m.isOpen(pn)) {
 292                 if (opened == null)
 293                     opened = new HashMap<>();
 294                 opened.computeIfAbsent(m, k -> new HashMap<>()).putIfAbsent(pn, how);
 295             }






 296         }
 297 
 298         /**
 299          * Builds the logger.
 300          */
 301         public IllegalAccessLogger build() {
 302             return new IllegalAccessLogger(exported, opened);
 303         }
 304     }
 305 
 306 
 307     static Map<Module, Map<String, String>> deepCopy(Map<Module, Map<String, String>> map) {
 308         if (map == null || map.isEmpty()) {
 309             return new HashMap<>();
 310         } else {
 311             Map<Module, Map<String, String>> newMap = new HashMap<>();
 312             for (Map.Entry<Module, Map<String, String>> e : map.entrySet()) {
 313                 newMap.put(e.getKey(), new HashMap<>(e.getValue()));
 314             }
 315             return newMap;
 316         }
 317     }
 318 }


   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.internal.module;
  27 
  28 import java.io.PrintStream;
  29 import java.lang.invoke.MethodHandles;
  30 import java.lang.reflect.Module;
  31 import java.net.URL;
  32 import java.security.AccessController;
  33 import java.security.CodeSource;
  34 import java.security.PrivilegedAction;
  35 import java.security.ProtectionDomain;
  36 import java.util.HashMap;
  37 import java.util.HashSet;
  38 import java.util.List;
  39 import java.util.Map;
  40 import java.util.Objects;
  41 import java.util.Set;
  42 import java.util.WeakHashMap;
  43 import java.util.function.Supplier;
  44 import java.util.stream.Collectors;
  45 
  46 import jdk.internal.loader.BootLoader;
  47 import sun.security.action.GetPropertyAction;
  48 
  49 /**
  50  * Supports logging of access to members of API packages that are exported or
  51  * opened via backdoor mechanisms to code in unnamed modules.
  52  */
  53 
  54 public final class IllegalAccessLogger {
  55 
  56     /**
  57      * Holder class to lazily create the StackWalker object and determine
  58      * if the stack trace should be printed
  59      */
  60     static class Holder {
  61         static final StackWalker STACK_WALKER;
  62         static final boolean PRINT_STACK_TRACE;
  63 
  64         static {
  65             PrivilegedAction<StackWalker> pa = () ->
  66                 StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
  67             STACK_WALKER = AccessController.doPrivileged(pa);
  68 
  69             String name = "sun.reflect.debugModuleAccessChecks";
  70             String value = GetPropertyAction.privilegedGetProperty(name, null);
  71             PRINT_STACK_TRACE = "access" .equals(value);
  72         }
  73     }



  74 
  75     // the maximum number of frames to capture
  76     private static final int MAX_STACK_FRAMES = 32;
  77 
  78     // lock to avoid interference when printing stack traces
  79     private static final Object OUTPUT_LOCK = new Object();
  80 
  81     // caller -> usages
  82     private final Map<Class<?>, Set<Usage>> callerToUsages = new WeakHashMap<>();
  83 
  84     // module -> (package name -> CLI option)
  85     private final Map<Module, Map<String, String>> exported;
  86     private final Map<Module, Map<String, String>> opened;
  87 
  88     // the print stream to send the warnings 
  89     private final PrintStream warningStream;
  90 
  91     private IllegalAccessLogger(Map<Module, Map<String, String>> exported,
  92                                 Map<Module, Map<String, String>> opened,
  93                                 PrintStream warningStream) {
  94         this.exported = deepCopy(exported);
  95         this.opened = deepCopy(opened);
  96         this.warningStream = warningStream;
  97     }
  98 
  99     /**
 100      * Returns that a Builder that is seeded with the packages known to this logger.
 101      */
 102     public Builder toBuilder() {
 103         return new Builder(exported, opened);
 104     }
 105 
 106     /**
 107      * Logs access to the member of a target class by a caller class if the class
 108      * is in a package that is exported via a backdoor mechanism.
 109      *
 110      * The {@code whatSupplier} supplies the message that describes the member.
 111      */
 112     public void logIfExportedByBackdoor(Class<?> caller,
 113                                         Class<?> target,
 114                                         Supplier<String> whatSupplier) {
 115         Map<String, String> packages = exported.get(target.getModule());
 116         if (packages != null) {


 169             if (how != null) {
 170                 log(caller.lookupClass(), target.getName(), () ->
 171                     "WARNING: Illegal access using Lookup on " + caller.lookupClass()
 172                     + " to " + target + " (permitted by " + how + ")");
 173             }
 174         }
 175     }
 176 
 177     /**
 178      * Log access by a caller. The {@code what} parameter describes the class or
 179      * member that is being accessed. The {@code msgSupplier} supplies the log
 180      * message.
 181      *
 182      * To reduce output, this method only logs the access if it hasn't been seen
 183      * previously. "Seen previously" is implemented as a map of caller class -> Usage,
 184      * where a Usage is the "what" and a hash of the stack trace. The map has weak
 185      * keys so it can be expunged when the caller is GC'ed/unloaded.
 186      */
 187     private void log(Class<?> caller, String what, Supplier<String> msgSupplier) {
 188         // stack trace without the top-most frames in java.base
 189         List<StackWalker.StackFrame> stack = Holder.STACK_WALKER.walk(s ->
 190             s.dropWhile(this::isJavaBase)
 191              .limit(MAX_STACK_FRAMES)
 192              .collect(Collectors.toList())
 193         );
 194 
 195         // check if the access has already been recorded
 196         Usage u = new Usage(what, hash(stack));
 197         boolean firstUsage;
 198         synchronized (this) {
 199             firstUsage = callerToUsages.computeIfAbsent(caller, k -> new HashSet<>()).add(u);
 200         }
 201 
 202         // log message if first usage
 203         if (firstUsage) {
 204             String msg = msgSupplier.get();
 205             if (Holder.PRINT_STACK_TRACE) {
 206                 synchronized (OUTPUT_LOCK) {
 207                     warningStream.println(msg);
 208                     stack.forEach(f -> warningStream.println("\tat " + f));
 209                 }
 210             } else {
 211                 warningStream.println(msg);
 212             }
 213         }
 214     }
 215 
 216     private static class Usage {
 217         private final String what;
 218         private final int stack;
 219         Usage(String what, int stack) {
 220             this.what = what;
 221             this.stack = stack;
 222         }
 223         @Override
 224         public int hashCode() {
 225             return what.hashCode() ^ stack;
 226         }
 227         @Override
 228         public boolean equals(Object ob) {
 229             if (ob instanceof Usage) {
 230                 Usage that = (Usage)ob;
 231                 return what.equals(that.what) && stack == (that.stack);


 266     public static void setIllegalAccessLogger(IllegalAccessLogger l) {
 267         if (l.exported.isEmpty() && l.opened.isEmpty()) {
 268             logger = null;
 269         } else {
 270             logger = l;
 271         }
 272     }
 273 
 274     /**
 275      * Returns the system-wide IllegalAccessLogger or {@code null} if there is
 276      * no logger.
 277      */
 278     public static IllegalAccessLogger illegalAccessLogger() {
 279         return logger;
 280     }
 281 
 282     /**
 283      * A builder for IllegalAccessLogger objects.
 284      */
 285     public static class Builder {
 286         private final Module UNNAMED = BootLoader.getUnnamedModule();
 287         private Map<Module, Map<String, String>> exported;
 288         private Map<Module, Map<String, String>> opened;
 289         private PrintStream warningStream = System.err;
 290 
 291         public Builder() { }
 292 
 293         public Builder(Map<Module, Map<String, String>> exported,
 294                        Map<Module, Map<String, String>> opened) {
 295             this.exported = deepCopy(exported);
 296             this.opened = deepCopy(opened);
 297         }
 298 
 299         public Builder logAccessToExportedPackage(Module m, String pn, String how) {
 300             if (!m.isExported(pn, UNNAMED)) {
 301                 if (exported == null)
 302                     exported = new HashMap<>();
 303                 exported.computeIfAbsent(m, k -> new HashMap<>()).putIfAbsent(pn, how);
 304             }
 305             return this;
 306         }
 307 
 308         public Builder logAccessToOpenPackage(Module m, String pn, String how) {
 309             // opens implies exported at run-time.
 310             logAccessToExportedPackage(m, pn, how);
 311 
 312             if (!m.isOpen(pn, UNNAMED)) {
 313                 if (opened == null)
 314                     opened = new HashMap<>();
 315                 opened.computeIfAbsent(m, k -> new HashMap<>()).putIfAbsent(pn, how);
 316             }
 317             return this;
 318         }
 319 
 320         public Builder warningStream(PrintStream warningStream) {
 321             this.warningStream = Objects.requireNonNull(warningStream);
 322             return this;
 323         }
 324 
 325         /**
 326          * Builds the logger.
 327          */
 328         public IllegalAccessLogger build() {
 329             return new IllegalAccessLogger(exported, opened, warningStream);
 330         }
 331     }
 332 
 333 
 334     static Map<Module, Map<String, String>> deepCopy(Map<Module, Map<String, String>> map) {
 335         if (map == null || map.isEmpty()) {
 336             return new HashMap<>();
 337         } else {
 338             Map<Module, Map<String, String>> newMap = new HashMap<>();
 339             for (Map.Entry<Module, Map<String, String>> e : map.entrySet()) {
 340                 newMap.put(e.getKey(), new HashMap<>(e.getValue()));
 341             }
 342             return newMap;
 343         }
 344     }
 345 }
< prev index next >